Skip to content

Commit

Permalink
Merge branch 'develop' into fix/promotion-index
Browse files Browse the repository at this point in the history
  • Loading branch information
riqwan authored Feb 26, 2025
2 parents b1d2c4c + 322d108 commit 53cff55
Show file tree
Hide file tree
Showing 143 changed files with 6,396 additions and 3,284 deletions.
6 changes: 6 additions & 0 deletions .changeset/clean-cars-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/product": patch
"@medusajs/utils": patch
---

chore(product): Remove upsertWithReplace where needed
5 changes: 5 additions & 0 deletions .changeset/eight-buckets-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/core-flows": patch
---

chore(core-flows): pass cart as reference to subflows
5 changes: 5 additions & 0 deletions .changeset/twenty-oranges-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/core-flows": patch
---

chore(core-flows): avoid overfetching to refresh cart
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ medusaIntegrationTestRunner({
)
})

it("should fail on non-existent product fields being present in the CSV", async () => {
it("should successfully skip non-existent product fields being present in the CSV", async () => {
const subscriberExecution = TestEventUtils.waitSubscribersExecution(
`${Modules.NOTIFICATION}.notification.${CommonEvents.CREATED}`,
eventBus
Expand Down Expand Up @@ -582,10 +582,21 @@ medusaIntegrationTestRunner({
expect.objectContaining({
data: expect.objectContaining({
title: "Product import",
description: `Failed to import products from file test.csv`,
description:
"Product import of file test.csv completed successfully!",
}),
})
)

const [importedProduct] = (
await api.get("/admin/products?limit=1&order=-id", adminHeaders)
).data.products

expect(importedProduct).not.toEqual(
expect.objectContaining({
field: "Test product",
})
)
})

it("supports importing the v1 template", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2068,7 +2068,7 @@ medusaIntegrationTestRunner({
unit_price: 4000,
is_custom_price: true,
quantity: 2,
title: "Test variant",
title: "Test item",
})
)
})
Expand Down
5 changes: 5 additions & 0 deletions packages/core/core-flows/src/cart/utils/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export const cartFieldsForRefreshSteps = [
"customer.*",
"customer.groups.*",
"promotions.code",
"payment_collection.id",
"payment_collection.raw_amount",
"payment_collection.amount",
"payment_collection.currency_code",
"payment_collection.payment_sessions.id",
]

export const completeCartFields = [
Expand Down
141 changes: 68 additions & 73 deletions packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
PromotionActions,
} from "@medusajs/framework/utils"
import {
createHook,
createWorkflow,
transform,
when,
Expand Down Expand Up @@ -41,57 +40,58 @@ export type RefreshCartItemsWorkflowInput = {
* These promotion codes will replace previously applied codes.
*/
promo_codes?: string[]
/**
* Force refresh the cart items
*/
force_refresh?: boolean
}

export const refreshCartItemsWorkflowId = "refresh-cart-items"
/**
* This workflow refreshes a cart to ensure its prices, promotion codes, taxes, and other details are applied correctly. It's useful
* after making a chnge to a cart, such as after adding an item to the cart or adding a promotion code.
*
*
* This workflow is used by other cart-related workflows, such as the {@link addToCartWorkflow} after an item
* is added to the cart.
*
*
* You can use this workflow within your own customizations or custom workflows, allowing you to refresh the cart after making updates to it in your
* custom flows.
*
*
* @example
* const { result } = await refreshCartItemsWorkflow(container)
* .run({
* input: {
* cart_id: "cart_123",
* }
* })
*
*
* @summary
*
*
* Refresh a cart's details after an update.
*
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
*
*/
export const refreshCartItemsWorkflow = createWorkflow(
refreshCartItemsWorkflowId,
(
input: WorkflowData<RefreshCartItemsWorkflowInput>
) => {
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart_id },
list: false,
})
(input: WorkflowData<RefreshCartItemsWorkflowInput>) => {
when({ input }, ({ input }) => {
return !!input.force_refresh
}).then(() => {
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart_id },
list: false,
})

const variantIds = transform({ cart }, (data) => {
return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean)
})
const variantIds = transform({ cart }, (data) => {
return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean)
})

const cartPricingContext = transform({ cart }, ({ cart }) => {
return filterObjectByKeys(cart, cartFieldsForPricingContext)
})
const cartPricingContext = transform({ cart }, ({ cart }) => {
return filterObjectByKeys(cart, cartFieldsForPricingContext)
})

const variants = when({ variantIds }, ({ variantIds }) => {
return !!variantIds.length
}).then(() => {
return useRemoteQueryStep({
const variants = useRemoteQueryStep({
entry_point: "variants",
fields: productVariantsFields,
variables: {
Expand All @@ -101,62 +101,59 @@ export const refreshCartItemsWorkflow = createWorkflow(
},
},
}).config({ name: "fetch-variants" })
})

validateVariantPricesStep({ variants })

const validate = createHook("validate", {
input,
cart,
})

const lineItems = transform({ cart, variants }, ({ cart, variants }) => {
const items = cart.items.map((item) => {
const variant = (variants ?? []).find((v) => v.id === item.variant_id)!

const input: PrepareLineItemDataInput = {
item,
variant: variant,
cartId: cart.id,
unitPrice: item.unit_price,
isTaxInclusive: item.is_tax_inclusive,
}

if (variant && !item.is_custom_price) {
input.unitPrice = variant.calculated_price?.calculated_amount
input.isTaxInclusive =
variant.calculated_price?.is_calculated_price_tax_inclusive
}

const preparedItem = prepareLineItemData(input)

return {
selector: { id: item.id },
data: preparedItem,
}
validateVariantPricesStep({ variants })

const lineItems = transform({ cart, variants }, ({ cart, variants }) => {
const items = cart.items.map((item) => {
const variant = (variants ?? []).find(
(v) => v.id === item.variant_id
)!

const input: PrepareLineItemDataInput = {
item,
variant: variant,
cartId: cart.id,
unitPrice: item.unit_price,
isTaxInclusive: item.is_tax_inclusive,
}

if (variant && !item.is_custom_price) {
input.unitPrice = variant.calculated_price?.calculated_amount
input.isTaxInclusive =
variant.calculated_price?.is_calculated_price_tax_inclusive
}

const preparedItem = prepareLineItemData(input)

return {
selector: { id: item.id },
data: preparedItem,
}
})

return items
})

return items
})

updateLineItemsStep({
id: cart.id,
items: lineItems,
updateLineItemsStep({
id: cart.id,
items: lineItems,
})
})

const refetchedCart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: cart.id },
variables: { id: input.cart_id },
list: false,
}).config({ name: "refetch–cart" })

refreshCartShippingMethodsWorkflow.runAsStep({
input: { cart_id: cart.id },
input: { cart: refetchedCart },
})

updateTaxLinesWorkflow.runAsStep({
input: { cart_id: cart.id },
input: { cart: refetchedCart },
})

const cartPromoCodes = transform(
Expand All @@ -172,18 +169,16 @@ export const refreshCartItemsWorkflow = createWorkflow(

updateCartPromotionsWorkflow.runAsStep({
input: {
cart_id: cart.id,
cart_id: input.cart_id,
promo_codes: cartPromoCodes,
action: PromotionActions.REPLACE,
},
})

refreshPaymentCollectionForCartWorkflow.runAsStep({
input: { cart_id: cart.id },
input: { cart_id: input.cart_id },
})

return new WorkflowResponse(refetchedCart, {
hooks: [validate],
})
return new WorkflowResponse(refetchedCart)
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,78 @@ import {
WorkflowData,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import { useQueryGraphStep } from "../../common"
import { useRemoteQueryStep } from "../../common"
import { removeShippingMethodFromCartStep } from "../steps"
import { updateShippingMethodsStep } from "../steps/update-shipping-methods"
import { listShippingOptionsForCartWithPricingWorkflow } from "./list-shipping-options-for-cart-with-pricing"

/**
* The details of the cart to refresh.
*/
export type RefreshCartShippingMethodsWorkflowInput = {
export type RefreshCartShippingMethodsWorkflowInput = {
/**
* The cart's ID.
*/
cart_id: string
cart_id?: string
/**
* The Cart reference.
*/
cart?: any
}

export const refreshCartShippingMethodsWorkflowId =
"refresh-cart-shipping-methods"
/**
* This workflow refreshes a cart's shipping methods, ensuring that their associated shipping options can still be used on the cart,
* and retrieve their correct pricing after a cart update. This workflow is used by the {@link refreshCartItemsWorkflow}.
*
*
* You can use this workflow within your own customizations or custom workflows, allowing you to refresh the cart's shipping method after making updates to the cart.
*
*
* @example
* const { result } = await refreshCartShippingMethodsWorkflow(container)
* .run({
* input: {
* cart_id: "cart_123",
* }
* })
*
*
* @summary
*
*
* Refresh a cart's shipping methods after an update.
*
*
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
*/
export const refreshCartShippingMethodsWorkflow = createWorkflow(
refreshCartShippingMethodsWorkflowId,
(input: WorkflowData<RefreshCartShippingMethodsWorkflowInput>) => {
const cartQuery = useQueryGraphStep({
entity: "cart",
filters: { id: input.cart_id },
fields: [
"id",
"sales_channel_id",
"currency_code",
"region_id",
"shipping_methods.*",
"shipping_address.city",
"shipping_address.country_code",
"shipping_address.province",
"shipping_methods.shipping_option_id",
"shipping_methods.data",
"total",
],
options: { throwIfKeyNotFound: true },
}).config({ name: "get-cart" })

const cart = transform({ cartQuery }, ({ cartQuery }) => cartQuery.data[0])
const fetchCart = when({ input }, ({ input }) => {
return !input.cart
}).then(() => {
return useRemoteQueryStep({
entry_point: "cart",
fields: [
"id",
"sales_channel_id",
"currency_code",
"region_id",
"shipping_methods.*",
"shipping_address.city",
"shipping_address.country_code",
"shipping_address.province",
"shipping_methods.shipping_option_id",
"shipping_methods.data",
"total",
],
variables: { id: input.cart_id },
throw_if_key_not_found: true,
list: false,
}).config({ name: "get-cart" })
})

const cart = transform({ fetchCart, input }, ({ fetchCart, input }) => {
return input.cart ?? fetchCart
})

const listShippingOptionsInput = transform({ cart }, ({ cart }) =>
(cart.shipping_methods || [])
.map((shippingMethod) => ({
Expand Down
Loading

0 comments on commit 53cff55

Please sign in to comment.