diff --git a/.changeset/empty-pillows-promise.md b/.changeset/empty-pillows-promise.md new file mode 100644 index 0000000000..2f768ad8c8 --- /dev/null +++ b/.changeset/empty-pillows-promise.md @@ -0,0 +1,7 @@ +--- +"@medusajs/test-utils": patch +"@medusajs/core-flows": patch +"@medusajs/framework": patch +--- + +feat(core-flows): Refresh adjustments when editing orders diff --git a/.gitignore b/.gitignore index 8ffa2bafe7..fb6d69ffde 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ dist/** # Cache File /packages/**/.cache + +.cursorignore diff --git a/integration-tests/http/__tests__/order-edits/order-edits.spec.ts b/integration-tests/http/__tests__/order-edits/order-edits.spec.ts index 54425336f5..90d4b27daf 100644 --- a/integration-tests/http/__tests__/order-edits/order-edits.spec.ts +++ b/integration-tests/http/__tests__/order-edits/order-edits.spec.ts @@ -1,8 +1,11 @@ import { medusaIntegrationTestRunner } from "@medusajs/test-utils" +import { IOrderModuleService, IPromotionModuleService } from "@medusajs/types" import { ContainerRegistrationKeys, Modules, OrderChangeStatus, + PromotionStatus, + PromotionType, RuleOperator, } from "@medusajs/utils" import { @@ -26,14 +29,18 @@ medusaIntegrationTestRunner({ let inventoryItemExtra let location let locationTwo + let buyRuleProduct let productExtra + let container + let region + let salesChannel const shippingProviderId = "manual_test-provider" beforeEach(async () => { - const container = getContainer() + container = getContainer() await createAdminUser(dbConnection, adminHeaders, container) - const region = ( + region = ( await api.post( "/admin/regions", { @@ -80,7 +87,7 @@ medusaIntegrationTestRunner({ ) ).data.tax_rate - const salesChannel = ( + salesChannel = ( await api.post( "/admin/sales-channels", { @@ -151,6 +158,31 @@ medusaIntegrationTestRunner({ ) ).data.product + buyRuleProduct = ( + await api.post( + "/admin/products", + { + title: "Buy rule product", + options: [{ title: "size", values: ["large", "small"] }], + shipping_profile_id: shippingProfile.id, + variants: [ + { + title: "buy rule variant", + sku: "buy-rule-variant-sku", + options: { size: "large" }, + prices: [ + { + currency_code: "usd", + amount: 10, + }, + ], + }, + ], + }, + adminHeaders + ) + ).data.product + const orderModule = container.resolve(Modules.ORDER) order = await orderModule.createOrders({ @@ -1145,5 +1177,493 @@ medusaIntegrationTestRunner({ ) }) }) + + describe("Order Edits promotions", () => { + let appliedPromotion + let promotionModule: IPromotionModuleService + let orderModule: IOrderModuleService + + beforeEach(async () => { + promotionModule = container.resolve(Modules.PROMOTION) + + appliedPromotion = await promotionModule.createPromotions({ + code: "PROMOTION_APPLIED", + type: PromotionType.STANDARD, + status: PromotionStatus.ACTIVE, + application_method: { + type: "percentage", + target_type: "order", + allocation: "across", + value: 10, + currency_code: "usd", + target_rules: [], + }, + }) + + orderModule = container.resolve(Modules.ORDER) + + order = await orderModule.createOrders({ + email: "foo@bar.com", + region_id: region.id, + sales_channel_id: salesChannel.id, + items: [ + { + // @ts-ignore + id: "item-1", + title: "Custom Item", + quantity: 1, + unit_price: 10, + }, + ], + shipping_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + phone: "12345", + }, + billing_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + }, + currency_code: "usd", + }) + + await orderModule.createOrderLineItemAdjustments([ + { + code: appliedPromotion.code!, + amount: 1, + item_id: "item-1", + promotion_id: appliedPromotion.id, + }, + ]) + + const remoteLink = container.resolve(ContainerRegistrationKeys.LINK) + + await remoteLink.create({ + [Modules.ORDER]: { order_id: order.id }, + [Modules.PROMOTION]: { promotion_id: appliedPromotion.id }, + }) + }) + + it("should update adjustments when adding a new item", async () => { + let result = await api.post( + "/admin/order-edits", + { + order_id: order.id, + description: "Test", + }, + adminHeaders + ) + + const orderId = result.data.order_change.order_id + + result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data + .order + + expect(result.original_total).toEqual(10) + expect(result.total).toEqual(9) + + // Add item with price $12 + $1.2 in taxes + result = ( + await api.post( + `/admin/order-edits/${orderId}/items`, + { + items: [ + { + variant_id: productExtra.variants[0].id, + quantity: 1, + }, + ], + }, + adminHeaders + ) + ).data.order_preview + + // 10% discount on two items of $12 and $10 = $2.2 + // Aside from this there is a tax rate of 10%, which adds ($1.2 - $0.12 (discount tax)) of taxes on the item of $12. + expect(result.total).toEqual(20.88) + expect(result.original_total).toEqual(23.2) + }) + + it("should update adjustments when updating an item", async () => { + let result = await api.post( + "/admin/order-edits", + { + order_id: order.id, + description: "Test", + }, + adminHeaders + ) + + const orderId = result.data.order_change.order_id + + const item = order.items[0] + + result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data + .order + + expect(result.original_total).toEqual(10) + expect(result.total).toEqual(9) + + let adjustments = result.items[0].adjustments + + expect(adjustments).toEqual([ + expect.objectContaining({ + amount: 1, + item_id: item.id, + }), + ]) + + // Update item quantity + result = ( + await api.post( + `/admin/order-edits/${orderId}/items/item/${item.id}`, + { + quantity: 2, + }, + adminHeaders + ) + ).data.order_preview + + expect(result.total).toEqual(18) + expect(result.original_total).toEqual(20) + + adjustments = result.items[0].adjustments + + expect(adjustments).toEqual([ + expect.objectContaining({ + amount: 2, + item_id: item.id, + }), + ]) + }) + + it("should update adjustments when removing an item", async () => { + let result = await api.post( + "/admin/order-edits", + { + order_id: order.id, + description: "Test", + }, + adminHeaders + ) + + const orderId = result.data.order_change.order_id + + const item = order.items[0] + + result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data + .order + + expect(result.original_total).toEqual(10) + expect(result.total).toEqual(9) + + let adjustments = result.items[0].adjustments + + expect(adjustments).toEqual([ + expect.objectContaining({ + amount: 1, + item_id: item.id, + }), + ]) + + result = ( + await api.post( + `/admin/order-edits/${orderId}/items`, + { + items: [ + { + variant_id: productExtra.variants[0].id, + quantity: 1, + }, + ], + }, + adminHeaders + ) + ).data.order_preview + + const orderItems = result.items + + expect(orderItems).toEqual([ + expect.objectContaining({ + adjustments: [ + expect.objectContaining({ + amount: 1, + item_id: item.id, + }), + ], + }), + expect.objectContaining({ + adjustments: [ + expect.objectContaining({ + amount: 1.2, + }), + ], + }), + ]) + + const newItem = result.items.find( + (item) => item.variant_id === productExtra.variants[0].id + ) + + const actionId = newItem.actions[0].id + + result = ( + await api.delete( + `/admin/order-edits/${orderId}/items/${actionId}`, + adminHeaders + ) + ).data.order_preview + + adjustments = result.items[0].adjustments + + expect(adjustments).toEqual([ + expect.objectContaining({ + amount: 1, + item_id: item.id, + }), + ]) + }) + + it("should not create adjustments when adding a new item if promotion is disabled", async () => { + let result = await api.post( + "/admin/order-edits", + { + order_id: order.id, + description: "Test", + }, + adminHeaders + ) + + const orderId = result.data.order_change.order_id + + result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data + .order + + expect(result.original_total).toEqual(10) + expect(result.total).toEqual(9) + + await api.post( + `/admin/promotions/${appliedPromotion.id}`, + { + status: "draft", + }, + adminHeaders + ) + + // Add item with price $12 + $1.2 in taxes + result = ( + await api.post( + `/admin/order-edits/${orderId}/items`, + { + items: [ + { + variant_id: productExtra.variants[0].id, + quantity: 1, + }, + ], + }, + adminHeaders + ) + ).data.order_preview + + expect(result.total).toEqual(23.2) + expect(result.original_total).toEqual(23.2) + }) + + it("should not change adjustments if order edit is canceled", async () => { + let result = await api.post( + "/admin/order-edits", + { + order_id: order.id, + description: "Test", + }, + adminHeaders + ) + + const orderId = result.data.order_change.order_id + + result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data + .order + + expect(result.original_total).toEqual(10) + expect(result.total).toEqual(9) + + // Add item with price $12 + $1.2 in taxes + result = ( + await api.post( + `/admin/order-edits/${orderId}/items`, + { + items: [ + { + variant_id: productExtra.variants[0].id, + quantity: 1, + }, + ], + }, + adminHeaders + ) + ).data.order_preview + + expect(result.total).toEqual(20.88) + expect(result.original_total).toEqual(23.2) + + await api.delete(`/admin/order-edits/${orderId}`, adminHeaders) + + result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data + .order + + expect(result.original_total).toEqual(10) + expect(result.total).toEqual(9) + }) + + it("should add, remove, and add buy-get adjustment depending on the quantity of the buy rule product", async () => { + promotionModule = container.resolve(Modules.PROMOTION) + + appliedPromotion = await promotionModule.createPromotions({ + code: "BUY_GET_PROMO", + type: "buyget", + status: "active", + application_method: { + allocation: "each", + value: 100, + max_quantity: 1, + type: "percentage", + target_type: "items", + apply_to_quantity: 1, + buy_rules_min_quantity: 2, + target_rules: [ + { + operator: "eq", + attribute: "items.product.id", + values: [productExtra.id], + }, + ], + buy_rules: [ + { + operator: "eq", + attribute: "items.product.id", + values: [buyRuleProduct.id], + }, + ], + }, + is_tax_inclusive: false, + is_automatic: true, + }) + + const orderModule: IOrderModuleService = container.resolve( + Modules.ORDER + ) + + order = await orderModule.createOrders({ + email: "foo@bar.com", + region_id: region.id, + sales_channel_id: salesChannel.id, + items: [ + { + variant_id: buyRuleProduct.variants[0].id, + quantity: 2, + title: "Buy rule product", + unit_price: 10, + product_id: buyRuleProduct.id, + }, + { + variant_id: productExtra.variants[0].id, + quantity: 1, + title: "Extra product", + unit_price: 10, + product_id: productExtra.id, + }, + ], + shipping_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + phone: "12345", + }, + billing_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + }, + currency_code: "usd", + }) + + await orderModule.createOrderLineItemAdjustments([ + { + code: appliedPromotion.code!, + amount: 1, + item_id: "item-1", + promotion_id: appliedPromotion.id, + }, + ]) + + const remoteLink = container.resolve(ContainerRegistrationKeys.LINK) + + await remoteLink.create({ + [Modules.ORDER]: { order_id: order.id }, + [Modules.PROMOTION]: { promotion_id: appliedPromotion.id }, + }) + + // Initially, the buy-get adjustment should be added to the order + let result = await api.post( + "/admin/order-edits", + { + order_id: order.id, + description: "Test", + }, + adminHeaders + ) + + const orderId = result.data.order_change.order_id + + result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data + .order + + expect(result.original_total).toEqual(30) + expect(result.total).toEqual(20) + + const buyRuleItem = result.items.find( + (item) => item.product_id === buyRuleProduct.id + ) + + // Update buy rule product quantity to 1 + // This should remove the buy-get adjustment, as it is no longer valid + result = ( + await api.post( + `/admin/order-edits/${orderId}/items/item/${buyRuleItem.id}`, + { + quantity: 1, + }, + adminHeaders + ) + ).data.order_preview + + expect(result.total).toEqual(20) + expect(result.original_total).toEqual(20) + + // Canceling the order edit should bring back the buy-get adjustment + await api.delete(`/admin/order-edits/${orderId}`, adminHeaders) + + result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data + .order + + expect(result.original_total).toEqual(30) + expect(result.total).toEqual(20) + }) + }) }, }) diff --git a/packages/core/core-flows/src/common/steps/use-remote-query.ts b/packages/core/core-flows/src/common/steps/use-remote-query.ts index 9db9c09781..6c32e4b47e 100644 --- a/packages/core/core-flows/src/common/steps/use-remote-query.ts +++ b/packages/core/core-flows/src/common/steps/use-remote-query.ts @@ -51,7 +51,7 @@ export const useRemoteQueryStepId = "use-remote-query" * This step fetches data across modules using the remote query. * * Learn more in the [Remote Query documentation](https://docs.medusajs.com/learn/fundamentals/module-links/query). - * + * * :::note * * This step is deprecated. Use {@link useQueryGraphStep} instead. diff --git a/packages/core/core-flows/src/draft-order/workflows/refresh-draft-order-adjustments.ts b/packages/core/core-flows/src/draft-order/workflows/refresh-draft-order-adjustments.ts index 65320f6b25..59a184b915 100644 --- a/packages/core/core-flows/src/draft-order/workflows/refresh-draft-order-adjustments.ts +++ b/packages/core/core-flows/src/draft-order/workflows/refresh-draft-order-adjustments.ts @@ -28,6 +28,15 @@ export interface RefreshDraftOrderAdjustmentsWorkflowInput { * The draft order to refresh the adjustments for. */ order: OrderDTO + + // TODO: I will reintroduce this type, once I have migrated all of the order flows to fit the expected type. + // Doing this in a single PR is too much work, so I'm going to do it in smaller PRs. + // + // order: Omit & { + // items?: ComputeActionItemLine[] + // promotions?: PromotionDTO[] + // } + /** * The promo codes to add or remove from the draft order. */ @@ -35,7 +44,7 @@ export interface RefreshDraftOrderAdjustmentsWorkflowInput { /** * The action to apply with the promo codes. You can * either: - * + * * - Add the promo codes to the draft order. * - Remove the promo codes from the draft order. * - Replace the existing promo codes with the new ones. @@ -47,10 +56,10 @@ export interface RefreshDraftOrderAdjustmentsWorkflowInput { * This workflow refreshes the adjustments or promotions for a draft order. It's used by other workflows * like {@link addDraftOrderItemsWorkflow} to refresh the promotions whenever changes * are made to the draft order. - * + * * You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around * refreshing the adjustments or promotions for a draft order. - * + * * @example * const { result } = await refreshDraftOrderAdjustmentsWorkflow(container) * .run({ @@ -61,9 +70,9 @@ export interface RefreshDraftOrderAdjustmentsWorkflowInput { * action: PromotionActions.ADD, * } * }) - * + * * @summary - * + * * Refresh the promotions in a draft order. */ export const refreshDraftOrderAdjustmentsWorkflow = createWorkflow( diff --git a/packages/core/core-flows/src/order/workflows/order-edit/begin-order-edit.ts b/packages/core/core-flows/src/order/workflows/order-edit/begin-order-edit.ts index 31331b2c94..8ae57efdc5 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/begin-order-edit.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/begin-order-edit.ts @@ -10,9 +10,11 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" -import { useRemoteQueryStep } from "../../../common" +import { useQueryGraphStep } from "../../../common" import { createOrderChangeStep } from "../../steps/create-order-change" import { throwIfOrderIsCancelled } from "../../utils/order-validation" +import { refreshOrderEditAdjustmentsWorkflow } from "./refresh-order-edit-adjustments" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that an order-edit can be requested for an order. @@ -27,14 +29,14 @@ export type BeginOrderEditValidationStepInput = { /** * This step validates that an order-edit 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 = beginOrderEditValidationStep({ * order: { @@ -54,13 +56,13 @@ export const beginOrderEditOrderWorkflowId = "begin-order-edit-order" /** * This workflow creates an order edit request. It' used by the * [Create Order Edit Admin API Route](https://docs.medusajs.com/api/admin#order-edits_postorderedits). - * - * To request the order edit, use the {@link requestOrderEditRequestWorkflow}. The order edit is then only applied after the + * + * To request the order edit, use the {@link requestOrderEditRequestWorkflow}. The order edit is then only applied after the * order edit is confirmed using the {@link confirmOrderEditRequestWorkflow}. - * + * * You can use this workflow within your customizations or your own custom workflows, allowing you to create an order edit * for an order in your custom flows. - * + * * @example * const { result } = await beginOrderEditOrderWorkflow(container) * .run({ @@ -68,9 +70,9 @@ export const beginOrderEditOrderWorkflowId = "begin-order-edit-order" * order_id: "order_123", * } * }) - * + * * @summary - * + * * Create an order edit request. */ export const beginOrderEditOrderWorkflow = createWorkflow( @@ -78,12 +80,17 @@ export const beginOrderEditOrderWorkflow = createWorkflow( function ( input: WorkflowData ): WorkflowResponse { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: ["id", "status"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, + }) + + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] }) beginOrderEditValidationStep({ order }) @@ -97,6 +104,13 @@ export const beginOrderEditOrderWorkflow = createWorkflow( internal_note: input.internal_note, } }) + + refreshOrderEditAdjustmentsWorkflow.runAsStep({ + input: { + order: order, + }, + }) + return new WorkflowResponse(createOrderChangeStep(orderChangeInput)) } ) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/cancel-begin-order-edit.ts b/packages/core/core-flows/src/order/workflows/order-edit/cancel-begin-order-edit.ts index dcf2d8daec..444dc81b14 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/cancel-begin-order-edit.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/cancel-begin-order-edit.ts @@ -11,12 +11,14 @@ import { parallelize, transform, } from "@medusajs/framework/workflows-sdk" -import { emitEventStep, useRemoteQueryStep } from "../../../common" +import { emitEventStep, useQueryGraphStep } from "../../../common" import { deleteOrderChangesStep, deleteOrderShippingMethods } from "../../steps" import { throwIfIsCancelled, throwIfOrderChangeIsNotActive, } from "../../utils/order-validation" +import { refreshOrderEditAdjustmentsWorkflow } from "./refresh-order-edit-adjustments" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that a requested order edit can be canceled. @@ -101,26 +103,35 @@ export const cancelBeginOrderEditWorkflow = createWorkflow( function ( input: WorkflowData ): WorkflowData { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: ["id", "version", "canceled_at"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, }).config({ name: "order-query" }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] + }) + + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", fields: ["id", "status", "version", "actions.*"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + cancelBeginOrderEditValidationStep({ order, orderChange }) const shippingToRemove = transform( @@ -150,5 +161,11 @@ export const cancelBeginOrderEditWorkflow = createWorkflow( data: eventData, }) ) + + refreshOrderEditAdjustmentsWorkflow.runAsStep({ + input: { + order: order, + }, + }) } ) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/confirm-order-edit-request.ts b/packages/core/core-flows/src/order/workflows/order-edit/confirm-order-edit-request.ts index 26df9f5682..202b343eec 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/confirm-order-edit-request.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/confirm-order-edit-request.ts @@ -21,7 +21,11 @@ import { prepareConfirmInventoryInput, requiredOrderFieldsForInventoryConfirmation, } from "../../../cart/utils/prepare-confirm-inventory-input" -import { emitEventStep, useRemoteQueryStep } from "../../../common" +import { + emitEventStep, + useQueryGraphStep, + useRemoteQueryStep, +} from "../../../common" import { deleteReservationsByLineItemsStep } from "../../../reservation" import { previewOrderChangeStep } from "../../steps" import { confirmOrderChanges } from "../../steps/confirm-order-changes" @@ -30,6 +34,7 @@ import { throwIfOrderChangeIsNotActive, } from "../../utils/order-validation" import { createOrUpdateOrderPaymentCollectionWorkflow } from "../create-or-update-order-payment-collection" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that a requested order edit can be confirmed. @@ -118,26 +123,21 @@ export const confirmOrderEditRequestWorkflow = createWorkflow( function ( input: ConfirmOrderEditRequestWorkflowInput ): WorkflowResponse { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: [ - "id", - "version", - "canceled_at", - "items.id", - "items.title", - "items.variant_title", - "items.variant_sku", - "items.variant_barcode", - "shipping_address.*", - ], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, }).config({ name: "order-query" }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] + }) + + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", fields: [ "id", "status", @@ -150,15 +150,19 @@ export const confirmOrderEditRequestWorkflow = createWorkflow( "actions.reference_id", "actions.internal_note", ], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + confirmOrderEditRequestValidationStep({ order, orderChange, diff --git a/packages/core/core-flows/src/order/workflows/order-edit/create-order-edit-shipping-method.ts b/packages/core/core-flows/src/order/workflows/order-edit/create-order-edit-shipping-method.ts index 3a9221ad68..ccaae376bb 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/create-order-edit-shipping-method.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/create-order-edit-shipping-method.ts @@ -13,6 +13,7 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" +import { pricingContextResult } from "../../../cart/utils/schemas" import { useRemoteQueryStep } from "../../../common" import { previewOrderChangeStep } from "../../steps" import { createOrderShippingMethods } from "../../steps/create-order-shipping-methods" @@ -23,7 +24,6 @@ import { import { prepareShippingMethod } from "../../utils/prepare-shipping-method" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" import { updateOrderTaxLinesWorkflow } from "../update-tax-lines" -import { pricingContextResult } from "../../../cart/utils/schemas" /** * The data to validate that a shipping method can be created for an order edit. diff --git a/packages/core/core-flows/src/order/workflows/order-edit/order-edit-add-new-item.ts b/packages/core/core-flows/src/order/workflows/order-edit/order-edit-add-new-item.ts index fb63fe25e2..43faea2eb4 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/order-edit-add-new-item.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/order-edit-add-new-item.ts @@ -4,7 +4,10 @@ import { OrderPreviewDTO, OrderWorkflow, } from "@medusajs/framework/types" -import { ChangeActionType, OrderChangeStatus } from "@medusajs/framework/utils" +import { + ChangeActionType, + OrderChangeStatus +} from "@medusajs/framework/utils" import { WorkflowData, WorkflowResponse, @@ -12,7 +15,7 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" -import { useRemoteQueryStep } from "../../../common" +import { useQueryGraphStep } from "../../../common" import { previewOrderChangeStep } from "../../steps/preview-order-change" import { throwIfIsCancelled, @@ -21,6 +24,8 @@ import { import { addOrderLineItemsWorkflow } from "../add-line-items" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" import { updateOrderTaxLinesWorkflow } from "../update-tax-lines" +import { refreshOrderEditAdjustmentsWorkflow } from "./refresh-order-edit-adjustments" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that new items can be added to an order edit. @@ -39,14 +44,14 @@ export type OrderEditAddNewItemValidationStepInput = { /** * This step validates that new items can be added to an order edit. * If the order is canceled or the order change is not active, the step will throw an error. - * + * * :::note - * + * * You can retrieve an order 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 = orderEditAddNewItemValidationStep({ * order: { @@ -74,10 +79,10 @@ export const orderEditAddNewItemWorkflowId = "order-edit-add-new-item" /** * This workflow adds new items to an order edit. It's used by the * [Add Items to Order Edit Admin API Route](https://docs.medusajs.com/api/admin#order-edits_postordereditsiditems). - * + * * You can use this workflow within your customizations or your own custom workflows, allowing you to add new items to an order edit * in your custom flows. - * + * * @example * const { result } = await orderEditAddNewItemWorkflow(container) * .run({ @@ -91,9 +96,9 @@ export const orderEditAddNewItemWorkflowId = "order-edit-add-new-item" * ] * } * }) - * + * * @summary - * + * * Add new items to an order edit. */ export const orderEditAddNewItemWorkflow = createWorkflow( @@ -101,26 +106,35 @@ export const orderEditAddNewItemWorkflow = createWorkflow( function ( input: WorkflowData ): WorkflowResponse { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: ["id", "status", "canceled_at", "items.*"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, }).config({ name: "order-query" }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", - fields: ["id", "status"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] + }) + + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", + fields: ["id", "status", "version", "actions.*"], + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + orderEditAddNewItemValidationStep({ order, orderChange, @@ -170,6 +184,12 @@ export const orderEditAddNewItemWorkflow = createWorkflow( input: orderChangeActionInput, }) + refreshOrderEditAdjustmentsWorkflow.runAsStep({ + input: { + order: order, + }, + }) + return new WorkflowResponse(previewOrderChangeStep(input.order_id)) } ) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/order-edit-update-item-quantity.ts b/packages/core/core-flows/src/order/workflows/order-edit/order-edit-update-item-quantity.ts index 1176eed94c..6e43336e9f 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/order-edit-update-item-quantity.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/order-edit-update-item-quantity.ts @@ -17,13 +17,15 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" -import { useRemoteQueryStep } from "../../../common" +import { useQueryGraphStep } from "../../../common" import { previewOrderChangeStep } from "../../steps/preview-order-change" import { throwIfIsCancelled, throwIfOrderChangeIsNotActive, } from "../../utils/order-validation" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" +import { refreshOrderEditAdjustmentsWorkflow } from "./refresh-order-edit-adjustments" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that the quantity of an existing item in an order can be updated in an order edit. @@ -42,14 +44,14 @@ export type OrderEditUpdateItemQuantityValidationStepInput = { /** * This step validates that the quantity of an existing item in an order can be updated in an order edit. * If the order is canceled or the order change is not active, the step will throw an error. - * + * * :::note - * + * * You can retrieve an order 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 = orderEditUpdateItemQuantityValidationStep({ * order: { @@ -78,12 +80,12 @@ export const orderEditUpdateItemQuantityWorkflowId = /** * This workflow updates the quantity of an existing item in an order's edit. It's used by the * [Update Order Item Quantity Admin API Route](https://docs.medusajs.com/api/admin#order-edits_postordereditsiditemsitemitem_id). - * + * * You can also use this workflow to remove an item from an order by setting its quantity to `0`. - * - * You can use this workflow within your customizations or your own custom workflows, allowing you to update the quantity of an existing + * + * You can use this workflow within your customizations or your own custom workflows, allowing you to update the quantity of an existing * item in an order's edit in your custom flow. - * + * * @example * const { result } = await orderEditUpdateItemQuantityWorkflow(container) * .run({ @@ -97,9 +99,9 @@ export const orderEditUpdateItemQuantityWorkflowId = * ] * } * }) - * + * * @summary - * + * * Update or remove an existing order item's quantity in the order's edit. */ export const orderEditUpdateItemQuantityWorkflow = createWorkflow( @@ -107,26 +109,35 @@ export const orderEditUpdateItemQuantityWorkflow = createWorkflow( function ( input: WorkflowData ): WorkflowResponse { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: ["id", "status", "canceled_at", "items.*"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, }).config({ name: "order-query" }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", - fields: ["id", "status"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] + }) + + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", + fields: ["id", "status", "version", "actions.*"], + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + orderEditUpdateItemQuantityValidationStep({ order, orderChange, @@ -166,6 +177,12 @@ export const orderEditUpdateItemQuantityWorkflow = createWorkflow( input: orderChangeActionInput, }) + refreshOrderEditAdjustmentsWorkflow.runAsStep({ + input: { + order: order, + }, + }) + return new WorkflowResponse(previewOrderChangeStep(input.order_id)) } ) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/refresh-order-edit-adjustments.ts b/packages/core/core-flows/src/order/workflows/order-edit/refresh-order-edit-adjustments.ts new file mode 100644 index 0000000000..b2f17fbe2b --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/order-edit/refresh-order-edit-adjustments.ts @@ -0,0 +1,79 @@ +import { PromotionActions } from "@medusajs/framework/utils" +import { + createWorkflow, + transform, + WorkflowData, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" +import { + ComputeActionItemLine, + OrderStatus, + PromotionDTO, +} from "@medusajs/types" +import { + refreshDraftOrderAdjustmentsWorkflow +} from "../../../draft-order/workflows/refresh-draft-order-adjustments" +import { previewOrderChangeStep } from "../../steps" + +export const refreshOrderEditAdjustmentsWorkflowId = + "refresh-order-edit-adjustments" + +/** + * The details of the order to refresh the adjustments for. + */ +export interface RefreshOrderEditAdjustmentsWorkflowInput { + /** + * The order edit to refresh the adjustments for. + */ + order: { + id: string + status: OrderStatus + currency_code: string + canceled_at?: string | Date + items: ComputeActionItemLine[] + promotions: PromotionDTO[] + } +} + +export const refreshOrderEditAdjustmentsWorkflow = createWorkflow( + refreshOrderEditAdjustmentsWorkflowId, + function (input: WorkflowData) { + const orderEditPromoCodes: string[] = transform({ input }, ({ input }) => { + return input.order.promotions + .map((p) => p?.code) + .filter(Boolean) as string[] + }) + + // we want the previewed order to contain updated promotions, + // so we fetch it to use it for refreshing adjustments + const orderPreview = previewOrderChangeStep(input.order.id).config({ + name: "order-preview", + }) + + const orderToRefresh = transform( + { input, orderPreview }, + ({ input, orderPreview }) => { + return { + ...orderPreview, + items: orderPreview.items.map((item) => ({ + ...item, + // Buy-Get promotions rely on the product ID, so we need to manually set it before refreshing adjustments + product: { id: item.product_id }, + })), + currency_code: input.order.currency_code, + promotions: input.order.promotions, + } + } + ) + + refreshDraftOrderAdjustmentsWorkflow.runAsStep({ + input: { + order: orderToRefresh, + promo_codes: orderEditPromoCodes, + action: PromotionActions.REPLACE, + }, + }) + + return new WorkflowResponse(void 0) + } +) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/remove-order-edit-item-action.ts b/packages/core/core-flows/src/order/workflows/order-edit/remove-order-edit-item-action.ts index ba18818784..0c110b46b0 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/remove-order-edit-item-action.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/remove-order-edit-item-action.ts @@ -5,14 +5,18 @@ import { OrderPreviewDTO, OrderWorkflow, } from "@medusajs/framework/types" -import { ChangeActionType, OrderChangeStatus } from "@medusajs/framework/utils" +import { + ChangeActionType, + OrderChangeStatus +} from "@medusajs/framework/utils" import { WorkflowData, WorkflowResponse, createStep, createWorkflow, + transform, } from "@medusajs/framework/workflows-sdk" -import { useRemoteQueryStep } from "../../../common" +import { useQueryGraphStep } from "../../../common" import { deleteOrderChangeActionsStep, previewOrderChangeStep, @@ -21,6 +25,8 @@ import { throwIfIsCancelled, throwIfOrderChangeIsNotActive, } from "../../utils/order-validation" +import { refreshOrderEditAdjustmentsWorkflow } from "./refresh-order-edit-adjustments" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that an item that was added in an order edit can be removed. @@ -41,17 +47,17 @@ export type RemoveOrderEditItemActionValidationStepInput = { } /** - * This step validates that an item that was added in the order edit can be removed + * This step validates that an item that was added in the order edit can be removed * from the order edit. If the order is canceled or the order change is not active, * the step will throw an error. - * + * * :::note - * + * * You can retrieve an order 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 = removeOrderEditItemActionValidationStep({ * order: { @@ -103,10 +109,10 @@ export const removeItemOrderEditActionWorkflowId = /** * This workflow removes an item that was added to an order edit. It's used by the * [Remove Item from Order Edit Admin API Route](https://docs.medusajs.com/api/admin#order-edits_deleteordereditsiditemsaction_id). - * - * You can use this workflow within your customizations or your own custom workflows, allowing you to remove an item that was + * + * You can use this workflow within your customizations or your own custom workflows, allowing you to remove an item that was * added to an order edit in your custom flow. - * + * * @example * const { result } = await removeItemOrderEditActionWorkflow(container) * .run({ @@ -115,9 +121,9 @@ export const removeItemOrderEditActionWorkflowId = * action_id: "orchact_123", * } * }) - * + * * @summary - * + * * Remove an item that was added to an order edit. */ export const removeItemOrderEditActionWorkflow = createWorkflow( @@ -125,26 +131,35 @@ export const removeItemOrderEditActionWorkflow = createWorkflow( function ( input: WorkflowData ): WorkflowResponse { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: ["id", "status", "canceled_at", "items.*"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, }).config({ name: "order-query" }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] + }) + + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", fields: ["id", "status", "version", "actions.*"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + removeOrderEditItemActionValidationStep({ order, input, @@ -153,6 +168,12 @@ export const removeItemOrderEditActionWorkflow = createWorkflow( deleteOrderChangeActionsStep({ ids: [input.action_id] }) + refreshOrderEditAdjustmentsWorkflow.runAsStep({ + input: { + order: order, + }, + }) + return new WorkflowResponse(previewOrderChangeStep(order.id)) } ) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/remove-order-edit-shipping-method.ts b/packages/core/core-flows/src/order/workflows/order-edit/remove-order-edit-shipping-method.ts index f455e34dbb..1915dd078c 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/remove-order-edit-shipping-method.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/remove-order-edit-shipping-method.ts @@ -13,7 +13,7 @@ import { parallelize, transform, } from "@medusajs/framework/workflows-sdk" -import { useRemoteQueryStep } from "../../../common" +import { useQueryGraphStep } from "../../../common" import { deleteOrderShippingMethods } from "../../steps" import { deleteOrderChangeActionsStep } from "../../steps/delete-order-change-actions" import { previewOrderChangeStep } from "../../steps/preview-order-change" @@ -30,21 +30,24 @@ export type RemoveOrderEditShippingMethodValidationStepInput = { /** * The details of the shipping method to be removed. */ - input: Pick + input: Pick< + OrderWorkflow.DeleteOrderEditShippingMethodWorkflowInput, + "order_id" | "action_id" + > } /** * This step validates that a shipping method can be removed from an order edit. * If the order change is not active, the shipping method isn't in the exchange, * or the action doesn't add a shipping method, the step will throw an error. - * + * * :::note - * + * * You can retrieve an 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 = removeOrderEditShippingMethodValidationStep({ * orderChange: { @@ -84,12 +87,12 @@ export const removeOrderEditShippingMethodValidationStep = createStep( export const removeOrderEditShippingMethodWorkflowId = "remove-order-edit-shipping-method" /** - * This workflow removes a shipping method of an order edit. It's used by the + * This workflow removes a shipping method of an order edit. It's used by the * [Remove Shipping Method Admin API Route](https://docs.medusajs.com/api/admin#order-edits_deleteordereditsidshippingmethodaction_id). - * - * You can use this workflow within your customizations or your own custom workflows, allowing you to remove a + * + * You can use this workflow within your customizations or your own custom workflows, allowing you to remove a * shipping method from an order edit in your custom flows. - * + * * @example * const { result } = await removeOrderEditShippingMethodWorkflow(container) * .run({ @@ -98,9 +101,9 @@ export const removeOrderEditShippingMethodWorkflowId = * action_id: "orchact_123", * } * }) - * + * * @summary - * + * * Remove a shipping method from an order edit. */ export const removeOrderEditShippingMethodWorkflow = createWorkflow( @@ -108,18 +111,22 @@ export const removeOrderEditShippingMethodWorkflow = createWorkflow( function ( input: WorkflowData ): WorkflowResponse { - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", fields: ["id", "status", "version", "actions.*"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + removeOrderEditShippingMethodValidationStep({ orderChange, input, diff --git a/packages/core/core-flows/src/order/workflows/order-edit/request-order-edit.ts b/packages/core/core-flows/src/order/workflows/order-edit/request-order-edit.ts index 7cd8ecb922..00b338edf2 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/request-order-edit.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/request-order-edit.ts @@ -13,13 +13,15 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" -import { emitEventStep, useRemoteQueryStep } from "../../../common" +import { emitEventStep, useQueryGraphStep } from "../../../common" import { previewOrderChangeStep } from "../../steps" import { updateOrderChangesStep } from "../../steps/update-order-changes" import { throwIfIsCancelled, throwIfOrderChangeIsNotActive, } from "../../utils/order-validation" +import { refreshOrderEditAdjustmentsWorkflow } from "./refresh-order-edit-adjustments" +import { fieldsToRefreshOrderEdit } from "./utils/fields" function getOrderChangesData({ input, @@ -127,26 +129,35 @@ export const requestOrderEditRequestWorkflow = createWorkflow( function ( input: OrderEditRequestWorkflowInput ): WorkflowResponse { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: ["id", "version", "canceled_at"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, }).config({ name: "order-query" }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", - fields: ["id", "canceled_at", "actions.*"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] + }) + + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", + fields: ["id", "status", "version", "actions.*"], + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + requestOrderEditRequestValidationStep({ order, orderChange, @@ -165,6 +176,12 @@ export const requestOrderEditRequestWorkflow = createWorkflow( } ) + refreshOrderEditAdjustmentsWorkflow.runAsStep({ + input: { + order: order, + }, + }) + emitEventStep({ eventName: OrderEditWorkflowEvents.REQUESTED, data: eventData, diff --git a/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-add-item.ts b/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-add-item.ts index eac3c87513..e326602c5e 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-add-item.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-add-item.ts @@ -13,7 +13,7 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" -import { useRemoteQueryStep } from "../../../common" +import { useQueryGraphStep } from "../../../common" import { previewOrderChangeStep, updateOrderChangeActionsStep, @@ -22,6 +22,8 @@ import { throwIfIsCancelled, throwIfOrderChangeIsNotActive, } from "../../utils/order-validation" +import { refreshOrderEditAdjustmentsWorkflow } from "./refresh-order-edit-adjustments" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that a new item can be updated in an order edit. @@ -43,17 +45,17 @@ export type UpdateOrderEditAddItemValidationStepInput = { /** * This step validates that a new item can be updated in an order edit. - * If the order is canceled, the order change is not active, + * If the order is canceled, the order change is not active, * the item isn't in the order edit, or the action isn't adding an item, * the step will throw an error. - * + * * :::note - * + * * You can retrieve an order 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 = updateOrderEditAddItemValidationStep({ * order: { @@ -76,11 +78,7 @@ export type UpdateOrderEditAddItemValidationStepInput = { export const updateOrderEditAddItemValidationStep = createStep( "update-order-edit-add-item-validation", async function ( - { - order, - orderChange, - input, - }: UpdateOrderEditAddItemValidationStepInput, + { order, orderChange, input }: UpdateOrderEditAddItemValidationStepInput, context ) { throwIfIsCancelled(order, "Order") @@ -104,10 +102,10 @@ export const updateOrderEditAddItemWorkflowId = "update-order-edit-add-item" /** * This workflow updates a new item in an order edit. It's used by the * [Update Item Admin API Route](https://docs.medusajs.com/api/admin#order-edits_postordereditsiditemsaction_id). - * + * * You can use this workflow within your customizations or your own custom workflows, allowing you to update a new item in an order edit * in your custom flows. - * + * * @example * const { result } = await updateOrderEditAddItemWorkflow(container) * .run({ @@ -119,9 +117,9 @@ export const updateOrderEditAddItemWorkflowId = "update-order-edit-add-item" * } * } * }) - * + * * @summary - * + * * Update a new item in an order edit. */ export const updateOrderEditAddItemWorkflow = createWorkflow( @@ -129,26 +127,35 @@ export const updateOrderEditAddItemWorkflow = createWorkflow( function ( input: WorkflowData ): WorkflowResponse { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: ["id", "status", "canceled_at", "items.*"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, }).config({ name: "order-query" }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] + }) + + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", fields: ["id", "status", "version", "actions.*"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + updateOrderEditAddItemValidationStep({ order, input, @@ -179,6 +186,12 @@ export const updateOrderEditAddItemWorkflow = createWorkflow( updateOrderChangeActionsStep([updateData]) + refreshOrderEditAdjustmentsWorkflow.runAsStep({ + input: { + order: order, + }, + }) + return new WorkflowResponse(previewOrderChangeStep(order.id)) } ) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-item-quantity.ts b/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-item-quantity.ts index 71cef3b44f..4915defb0f 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-item-quantity.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-item-quantity.ts @@ -13,7 +13,7 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" -import { useRemoteQueryStep } from "../../../common" +import { useQueryGraphStep } from "../../../common" import { previewOrderChangeStep, updateOrderChangeActionsStep, @@ -22,6 +22,8 @@ import { throwIfIsCancelled, throwIfOrderChangeIsNotActive, } from "../../utils/order-validation" +import { refreshOrderEditAdjustmentsWorkflow } from "./refresh-order-edit-adjustments" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that an existing order item can be updated in an order edit. @@ -46,14 +48,14 @@ export type UpdateOrderEditItemQuantityValidationStepInput = { * If the order is canceled, the order change is not active, * the item isn't in the order edit, or the action isn't updating an existing item, * the step will throw an error. - * + * * :::note - * + * * You can retrieve an order 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 = updateOrderEditItemQuantityValidationStep({ * order: { @@ -104,10 +106,10 @@ export const updateOrderEditItemQuantityWorkflowId = "update-order-edit-update-quantity" /** * This workflow updates an existing order item that was previously added to the order edit. - * - * You can use this workflow within your customizations or your own custom workflows, allowing you to update the quantity + * + * You can use this workflow within your customizations or your own custom workflows, allowing you to update the quantity * of an existing item in an order edit in your custom flows. - * + * * @example * const { result } = await updateOrderEditItemQuantityWorkflow(container) * .run({ @@ -119,9 +121,9 @@ export const updateOrderEditItemQuantityWorkflowId = * } * } * }) - * + * * @summary - * + * * Update an existing order item previously added to an order edit. */ export const updateOrderEditItemQuantityWorkflow = createWorkflow( @@ -129,26 +131,35 @@ export const updateOrderEditItemQuantityWorkflow = createWorkflow( function ( input: WorkflowData ): WorkflowResponse { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "orders", - fields: ["id", "status", "canceled_at", "items.*"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, }).config({ name: "order-query" }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] + }) + + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", fields: ["id", "status", "version", "actions.*"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + updateOrderEditItemQuantityValidationStep({ order, input, @@ -175,6 +186,12 @@ export const updateOrderEditItemQuantityWorkflow = createWorkflow( updateOrderChangeActionsStep([updateData]) + refreshOrderEditAdjustmentsWorkflow.runAsStep({ + input: { + order: order, + }, + }) + return new WorkflowResponse(previewOrderChangeStep(order.id)) } ) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-shipping-method.ts b/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-shipping-method.ts index dbf91c98fe..b5fb000ac2 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-shipping-method.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-shipping-method.ts @@ -2,7 +2,6 @@ import { AdditionalData, OrderChangeActionDTO, OrderChangeDTO, - OrderDTO, OrderPreviewDTO, OrderWorkflow, } from "@medusajs/framework/types" @@ -17,7 +16,8 @@ import { transform, when, } from "@medusajs/framework/workflows-sdk" -import { useRemoteQueryStep } from "../../../common" +import { pricingContextResult } from "../../../cart/utils/schemas" +import { useQueryGraphStep, useRemoteQueryStep } from "../../../common" import { updateOrderChangeActionsStep, updateOrderShippingMethodsStep, @@ -25,7 +25,7 @@ import { import { previewOrderChangeStep } from "../../steps/preview-order-change" import { throwIfOrderChangeIsNotActive } from "../../utils/order-validation" import { prepareShippingMethodUpdate } from "../../utils/prepare-shipping-method" -import { pricingContextResult } from "../../../cart/utils/schemas" +import { fieldsToRefreshOrderEdit } from "./utils/fields" /** * The data to validate that an order edit's shipping method can be updated. @@ -119,11 +119,11 @@ export const updateOrderEditShippingMethodWorkflowId = * @summary * * Update a shipping method of an order edit. - * + * * @property hooks.setPricingContext - This hook is executed before the shipping method's option is retrieved. You can consume this hook to return any custom context useful for the prices retrieval of shipping method's option. - * + * * For example, assuming you have the following custom pricing rule: - * + * * ```json * { * "attribute": "location_id", @@ -131,13 +131,13 @@ export const updateOrderEditShippingMethodWorkflowId = * "value": "sloc_123", * } * ``` - * + * * You can consume the `setPricingContext` hook to add the `location_id` context to the prices calculation: - * + * * ```ts * import { updateOrderEditShippingMethodWorkflow } from "@medusajs/medusa/core-flows"; * import { StepResponse } from "@medusajs/workflows-sdk"; - * + * * updateOrderEditShippingMethodWorkflow.hooks.setPricingContext(( * { order, order_change, additional_data }, { container } * ) => { @@ -146,13 +146,13 @@ export const updateOrderEditShippingMethodWorkflowId = * }); * }); * ``` - * + * * The price of the shipping method's option will now be retrieved using the context you return. - * + * * :::note - * + * * Learn more about prices calculation context in the [Prices Calculation](https://docs.medusajs.com/resources/commerce-modules/pricing/price-calculation) documentation. - * + * * ::: */ export const updateOrderEditShippingMethodWorkflow = createWorkflow( @@ -162,26 +162,35 @@ export const updateOrderEditShippingMethodWorkflow = createWorkflow( OrderWorkflow.UpdateOrderEditShippingMethodWorkflowInput & AdditionalData > ) { - const order: OrderDTO = useRemoteQueryStep({ - entry_point: "order_claim", - fields: ["id", "currency_code"], - variables: { id: input.order_id }, - list: false, - throw_if_key_not_found: true, + const orderResult = useQueryGraphStep({ + entity: "order", + fields: fieldsToRefreshOrderEdit, + filters: { id: input.order_id }, + options: { + throwIfKeyNotFound: true, + }, + }).config({ name: "order-query" }) + + const order = transform({ orderResult }, ({ orderResult }) => { + return orderResult.data[0] }) - const orderChange: OrderChangeDTO = useRemoteQueryStep({ - entry_point: "order_change", + const orderChangeResult = useQueryGraphStep({ + entity: "order_change", fields: ["id", "status", "version", "actions.*"], - variables: { - filters: { - order_id: input.order_id, - status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], - }, + filters: { + order_id: input.order_id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], }, - list: false, }).config({ name: "order-change-query" }) + const orderChange = transform( + { orderChangeResult }, + ({ orderChangeResult }) => { + return orderChangeResult.data[0] + } + ) + const setPricingContext = createHook( "setPricingContext", { diff --git a/packages/core/core-flows/src/order/workflows/order-edit/utils/fields.ts b/packages/core/core-flows/src/order/workflows/order-edit/utils/fields.ts new file mode 100644 index 0000000000..948203b41e --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/order-edit/utils/fields.ts @@ -0,0 +1,11 @@ +export const fieldsToRefreshOrderEdit = [ + "id", + "status", + "version", + "currency_code", + "canceled_at", + "items.*", + "items.product.id", + "promotions.*", + "shipping_address.*", +] diff --git a/packages/core/framework/package.json b/packages/core/framework/package.json index d8b92a18c6..80a618704b 100644 --- a/packages/core/framework/package.json +++ b/packages/core/framework/package.json @@ -85,7 +85,7 @@ "@opentelemetry/api": "^1.9.0", "@types/express": "^4.17.17", "chokidar": "^3.4.2", - "compression": "1.7.4", + "compression": "1.8.0", "connect-redis": "5.2.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", diff --git a/packages/core/types/src/promotion/common/compute-actions.ts b/packages/core/types/src/promotion/common/compute-actions.ts index da40d48a62..1ea6cb5ab6 100644 --- a/packages/core/types/src/promotion/common/compute-actions.ts +++ b/packages/core/types/src/promotion/common/compute-actions.ts @@ -199,6 +199,13 @@ export interface ComputeActionItemLine extends Record { * The adjustments applied before on the line item. */ adjustments?: ComputeActionAdjustmentLine[] + + /** + * The product ID of the line item. Our default promotion rules rely on the product ID to apply the promotion. + */ + product?: { + id: string + } } /** diff --git a/packages/medusa-test-utils/src/database.ts b/packages/medusa-test-utils/src/database.ts index 8a0ee9d2bc..9471efab55 100644 --- a/packages/medusa-test-utils/src/database.ts +++ b/packages/medusa-test-utils/src/database.ts @@ -1,3 +1,4 @@ +import { logger } from "@medusajs/framework/logger" import { defineConfig, MikroORM, @@ -5,7 +6,6 @@ import { SqlEntityManager, } from "@mikro-orm/postgresql" import { createDatabase, dropDatabase } from "pg-god" -import { logger } from "@medusajs/framework/logger" import { execOrTimeout } from "./medusa-test-runner-utils" const DB_HOST = process.env.DB_HOST ?? "localhost" diff --git a/yarn.lock b/yarn.lock index b7f39d753b..c917d749bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6324,7 +6324,7 @@ __metadata: "@types/jsonwebtoken": ^8.5.9 awilix: ^8.0.1 chokidar: ^3.4.2 - compression: 1.7.4 + compression: 1.8.0 connect-dynamodb: ^3.0.5 connect-redis: 5.2.0 cookie-parser: ^1.4.6 @@ -18610,7 +18610,7 @@ __metadata: languageName: node linkType: hard -"compression@npm:^1.7.5": +"compression@npm:1.8.0, compression@npm:^1.7.5": version: 1.8.0 resolution: "compression@npm:1.8.0" dependencies: