diff --git a/.changeset/eight-spies-call.md b/.changeset/eight-spies-call.md new file mode 100644 index 0000000000..9534c8e32e --- /dev/null +++ b/.changeset/eight-spies-call.md @@ -0,0 +1,7 @@ +--- +"@medusajs/core-flows": patch +"@medusajs/js-sdk": patch +"@medusajs/medusa": patch +--- + +feat(core-flows,js-sdk,medusa): draft order shipping removal diff --git a/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts b/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts index 0efbc0d44f..d1cf775509 100644 --- a/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts +++ b/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts @@ -15,6 +15,7 @@ medusaIntegrationTestRunner({ let salesChannel: HttpTypes.AdminSalesChannel let stockLocation: HttpTypes.AdminStockLocation let testDraftOrder: HttpTypes.AdminDraftOrder + let shippingOption: HttpTypes.AdminShippingOption beforeEach(async () => { const container = getContainer() @@ -46,12 +47,69 @@ medusaIntegrationTestRunner({ ) ).data.stock_location + const shippingProfile = ( + await api.post( + `/admin/shipping-profiles`, + { name: "test shipping profile", type: "default" }, + adminHeaders + ) + ).data.shipping_profile + + const fulfillmentSets = ( + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`, + { + name: `Test-${shippingProfile.id}`, + type: "test-type", + }, + adminHeaders + ) + ).data.stock_location.fulfillment_sets + + const fulfillmentSet = ( + await api.post( + `/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`, + { + name: `Test-${shippingProfile.id}`, + geo_zones: [{ type: "country", country_code: "us" }], + }, + adminHeaders + ) + ).data.fulfillment_set + + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-providers`, + { add: ["manual_test-provider"] }, + adminHeaders + ) + await api.post( `/admin/stock-locations/${stockLocation.id}/sales-channels`, { add: [salesChannel.id] }, adminHeaders ) + shippingOption = ( + await api.post( + `/admin/shipping-options`, + { + name: `Test shipping option ${fulfillmentSet.id}`, + service_zone_id: fulfillmentSet.service_zones[0].id, + shipping_profile_id: shippingProfile.id, + provider_id: "manual_test-provider", + price_type: "flat", + type: { + label: "Test type", + description: "Test description", + code: "test-code", + }, + prices: [{ currency_code: "usd", amount: 1000 }], + rules: [], + }, + adminHeaders + ) + ).data.shipping_option + testDraftOrder = ( await api.post( "/admin/draft-orders", @@ -286,5 +344,141 @@ medusaIntegrationTestRunner({ ) }) }) + + describe("DELETE /draft-orders/:id/shipping-options/methods/:method_id", () => { + let product + let edit + + beforeEach(async () => { + const inventoryItem = ( + await api.post( + `/admin/inventory-items`, + { sku: "shirt" }, + adminHeaders + ) + ).data.inventory_item + + await api.post( + `/admin/inventory-items/${inventoryItem.id}/location-levels`, + { + location_id: stockLocation.id, + stocked_quantity: 10, + }, + adminHeaders + ) + + product = ( + await api.post( + "/admin/products", + { + title: "Shirt", + options: [{ title: "size", values: ["large", "small"] }], + variants: [ + { + title: "L shirt", + options: { size: "large" }, + inventory_items: [ + { + inventory_item_id: inventoryItem.id, + required_quantity: 1, + }, + ], + prices: [ + { + currency_code: "usd", + amount: 10, + }, + ], + }, + { + title: "S shirt", + options: { size: "small" }, + inventory_items: [ + { + inventory_item_id: inventoryItem.id, + required_quantity: 1, + }, + ], + prices: [ + { + currency_code: "usd", + amount: 10, + }, + ], + }, + ], + }, + adminHeaders + ) + ).data.product + + edit = ( + await api.post( + `/admin/draft-orders/${testDraftOrder.id}/edit`, + {}, + adminHeaders + ) + ).data.draft_order_preview + + await api.post( + `/admin/draft-orders/${testDraftOrder.id}/edit/items`, + { + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + }, + adminHeaders + ) + + await api.post( + `/admin/draft-orders/${testDraftOrder.id}/edit/shipping-methods`, + { + shipping_option_id: shippingOption.id, + }, + adminHeaders + ) + + edit = ( + await api.post( + `/admin/draft-orders/${testDraftOrder.id}/edit/confirm`, + {}, + adminHeaders + ) + ).data.draft_order_preview + }) + + it("should delete a shipping method from the draft order", async () => { + edit = ( + await api.post( + `/admin/draft-orders/${testDraftOrder.id}/edit`, + {}, + adminHeaders + ) + ).data.draft_order_preview + + const response = await api.delete( + `/admin/draft-orders/${testDraftOrder.id}/edit/shipping-methods/method/${edit.shipping_methods[0].id}`, + adminHeaders + ) + + expect(response.status).toBe(200) + expect(response.data.draft_order_preview.shipping_methods.length).toBe( + 0 + ) + + await api.post( + `/admin/draft-orders/${testDraftOrder.id}/edit/confirm`, + {}, + adminHeaders + ) + + const order = ( + await api.get( + `/admin/draft-orders/${testDraftOrder.id}`, + adminHeaders + ) + ).data.draft_order + + expect(order.shipping_methods.length).toBe(0) + }) + }) }, }) diff --git a/packages/core/core-flows/src/draft-order/workflows/add-draft-order-shipping-methods.ts b/packages/core/core-flows/src/draft-order/workflows/add-draft-order-shipping-methods.ts index 5b47f6e5c3..147f349445 100644 --- a/packages/core/core-flows/src/draft-order/workflows/add-draft-order-shipping-methods.ts +++ b/packages/core/core-flows/src/draft-order/workflows/add-draft-order-shipping-methods.ts @@ -149,7 +149,7 @@ export const addDraftOrderShippingMethodsWorkflow = createWorkflow( order, shippingOptions, createdMethods, - customPrice: input.custom_amount as any, // Need to cast this to any otherwise the type becomes to complex. + customPrice: input.custom_amount as any, // Need to cast this to any otherwise the type becomes too complex. orderChange, }, ({ diff --git a/packages/core/core-flows/src/draft-order/workflows/begin-draft-order-edit.ts b/packages/core/core-flows/src/draft-order/workflows/begin-draft-order-edit.ts index 31ed8cd0e3..efc643e60e 100644 --- a/packages/core/core-flows/src/draft-order/workflows/begin-draft-order-edit.ts +++ b/packages/core/core-flows/src/draft-order/workflows/begin-draft-order-edit.ts @@ -6,7 +6,7 @@ import { } from "@medusajs/framework/workflows-sdk" import { OrderDTO, OrderWorkflow } from "@medusajs/types" import { useRemoteQueryStep } from "../../common" -import { createOrderChangeStep } from "../../order" +import { createOrderChangeStep, previewOrderChangeStep } from "../../order" import { validateDraftOrderStep } from "../steps" export const beginDraftOrderEditWorkflowId = "begin-draft-order-edit" @@ -34,6 +34,8 @@ export const beginDraftOrderEditWorkflow = createWorkflow( } }) - return new WorkflowResponse(createOrderChangeStep(orderChangeInput)) + createOrderChangeStep(orderChangeInput) + + return new WorkflowResponse(previewOrderChangeStep(input.order_id)) } ) diff --git a/packages/core/core-flows/src/draft-order/workflows/cancel-draft-order-edit.ts b/packages/core/core-flows/src/draft-order/workflows/cancel-draft-order-edit.ts index 981e658deb..95468a19ce 100644 --- a/packages/core/core-flows/src/draft-order/workflows/cancel-draft-order-edit.ts +++ b/packages/core/core-flows/src/draft-order/workflows/cancel-draft-order-edit.ts @@ -54,7 +54,7 @@ export const cancelDraftOrderEditWorkflow = createWorkflow( ({ orderChange }) => { return (orderChange.actions ?? []) .filter((a) => a.action === ChangeActionType.SHIPPING_ADD) - .map(({ id }) => id) + .map(({ reference_id }) => reference_id) } ) diff --git a/packages/core/core-flows/src/draft-order/workflows/index.ts b/packages/core/core-flows/src/draft-order/workflows/index.ts index 8040f0f300..178eda788e 100644 --- a/packages/core/core-flows/src/draft-order/workflows/index.ts +++ b/packages/core/core-flows/src/draft-order/workflows/index.ts @@ -14,3 +14,4 @@ export * from "./update-draft-order-action-item" export * from "./update-draft-order-action-shipping-method" export * from "./update-draft-order-item" export * from "./update-draft-order-shipping-method" +export * from "./remove-draft-order-shipping-method" diff --git a/packages/core/core-flows/src/draft-order/workflows/remove-draft-order-shipping-method.ts b/packages/core/core-flows/src/draft-order/workflows/remove-draft-order-shipping-method.ts new file mode 100644 index 0000000000..ea88396651 --- /dev/null +++ b/packages/core/core-flows/src/draft-order/workflows/remove-draft-order-shipping-method.ts @@ -0,0 +1,130 @@ +import { + ChangeActionType, + OrderChangeStatus, + PromotionActions, +} from "@medusajs/framework/utils" +import { + createWorkflow, + transform, + when, + WorkflowData, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" +import { OrderChangeDTO, OrderDTO } from "@medusajs/types" +import { useRemoteQueryStep } from "../../common" +import { + createOrderChangeActionsWorkflow, + previewOrderChangeStep, + updateOrderTaxLinesWorkflow, +} from "../../order" +import { validateDraftOrderChangeStep } from "../steps/validate-draft-order-change" +import { draftOrderFieldsForRefreshSteps } from "../utils/fields" +import { refreshDraftOrderAdjustmentsWorkflow } from "./refresh-draft-order-adjustments" + +export const removeDraftOrderShippingMethodWorkflowId = + "remove-draft-order-shipping-method" + +interface RemoveDraftOrderShippingMethodWorkflowInput { + /** + * The ID of the draft order to add the shipping methods to. + */ + order_id: string + /** + * The ID of the shipping method to remove. + */ + shipping_method_id: string +} + +export const removeDraftOrderShippingMethodWorkflow = createWorkflow( + removeDraftOrderShippingMethodWorkflowId, + function (input: WorkflowData) { + const order: OrderDTO = useRemoteQueryStep({ + entry_point: "orders", + fields: draftOrderFieldsForRefreshSteps, + variables: { id: input.order_id }, + list: false, + throw_if_key_not_found: true, + }).config({ name: "order-query" }) + + const orderChange: OrderChangeDTO = useRemoteQueryStep({ + entry_point: "order_change", + fields: ["id", "status", "version"], + variables: { + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], + }, + }, + list: false, + }).config({ name: "order-change-query" }) + + validateDraftOrderChangeStep({ order, orderChange }) + + updateOrderTaxLinesWorkflow.runAsStep({ + input: { + order_id: order.id, + }, + }) + + const appliedPromoCodes = transform(order, (order) => { + const promotionLink = (order as any).promotion_link + + if (!promotionLink) { + return [] + } + + if (Array.isArray(promotionLink)) { + return promotionLink.map((promo) => promo.promotion.code) + } + + return [promotionLink.promotion.code] + }) + + // If any the order has any promo codes, then we need to refresh the adjustments. + when( + appliedPromoCodes, + (appliedPromoCodes) => appliedPromoCodes.length > 0 + ).then(() => { + const refetchedOrder = useRemoteQueryStep({ + entry_point: "orders", + fields: draftOrderFieldsForRefreshSteps, + variables: { id: input.order_id }, + list: false, + throw_if_key_not_found: true, + }).config({ name: "refetched-order-query" }) + + refreshDraftOrderAdjustmentsWorkflow.runAsStep({ + input: { + order: refetchedOrder, + promo_codes: appliedPromoCodes, + action: PromotionActions.REPLACE, + }, + }) + }) + + const orderChangeActionInput = transform( + { + input, + order, + orderChange, + }, + ({ order, orderChange, input }) => { + return [ + { + action: ChangeActionType.SHIPPING_REMOVE, + reference: "order_shipping_method", + order_change_id: orderChange.id, + reference_id: input.shipping_method_id, + order_id: order.id, + }, + ] + } + ) + + createOrderChangeActionsWorkflow.runAsStep({ + input: orderChangeActionInput, + }) + + return new WorkflowResponse(previewOrderChangeStep(order.id)) + } +) diff --git a/packages/core/core-flows/src/order/workflows/create-order-change-actions.ts b/packages/core/core-flows/src/order/workflows/create-order-change-actions.ts index d8a1675538..ba5ffb3e93 100644 --- a/packages/core/core-flows/src/order/workflows/create-order-change-actions.ts +++ b/packages/core/core-flows/src/order/workflows/create-order-change-actions.ts @@ -14,12 +14,12 @@ export const createOrderChangeActionsWorkflowId = "create-order-change-actions" /** * This workflow creates order change actions. It's used by other order-related workflows, * such as {@link requestItemReturnWorkflow} to create an order change action based on changes made to the order. - * - * You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around + * + * You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around * creating an order change action. - * + * * @summary - * + * * Create an order change action. */ export const createOrderChangeActionsWorkflow = createWorkflow( diff --git a/packages/core/js-sdk/src/admin/draft-order.ts b/packages/core/js-sdk/src/admin/draft-order.ts index 445a93e12e..5a820d2a25 100644 --- a/packages/core/js-sdk/src/admin/draft-order.ts +++ b/packages/core/js-sdk/src/admin/draft-order.ts @@ -506,16 +506,30 @@ export class DraftOrder { ) } + async removeShippingMethod( + id: string, + shippingMethodId: string, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/draft-orders/${id}/edit/shipping-methods/method/${shippingMethodId}`, + { + method: "DELETE", + headers, + } + ) + } + /** * This method updates a shipping method in a draft order. It sends a request to the * [Update Draft Order Shipping Method](https://docs.medusajs.com/api/admin#draft-orders_postordereditsidshipping-methodsaction_id) API route. - * + * * @param id - The draft order's ID. * @param methodId - The shipping method's ID. * @param body - The data to update the shipping method. * @param headers - Headers to pass in the request. * @returns The draft order preview's details. - * + * * @example * sdk.admin.draftOrder.updateShippingMethod("order_123", "sm_123", { * shipping_option_id: "so_123", diff --git a/packages/medusa/src/api/admin/draft-orders/[id]/edit/shipping-methods/method/[method_id]/route.ts b/packages/medusa/src/api/admin/draft-orders/[id]/edit/shipping-methods/method/[method_id]/route.ts index 1633c6c176..07bc1e926e 100644 --- a/packages/medusa/src/api/admin/draft-orders/[id]/edit/shipping-methods/method/[method_id]/route.ts +++ b/packages/medusa/src/api/admin/draft-orders/[id]/edit/shipping-methods/method/[method_id]/route.ts @@ -1,4 +1,7 @@ -import { updateDraftOrderShippingMethodWorkflow } from "@medusajs/core-flows" +import { + removeDraftOrderShippingMethodWorkflow, + updateDraftOrderShippingMethodWorkflow, +} from "@medusajs/core-flows" import { AuthenticatedMedusaRequest, MedusaResponse, @@ -25,3 +28,23 @@ export const POST = async ( draft_order_preview: result as unknown as HttpTypes.AdminDraftOrderPreview, }) } + +export const DELETE = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id, method_id } = req.params + + const { result } = await removeDraftOrderShippingMethodWorkflow( + req.scope + ).run({ + input: { + order_id: id, + shipping_method_id: method_id, + }, + }) + + res.json({ + draft_order_preview: result as unknown as HttpTypes.AdminDraftOrderPreview, + }) +}