Skip to content

Commit

Permalink
fix(carts): Fixes cart modifications not accounting for certain price…
Browse files Browse the repository at this point in the history
… lists (#10493)

*What*

* Fixes #10490
* Expands any available customer_id into its customer_group_ids for cart
  updates that add line items.

*Why*

* Cart updates from the storefront were overriding any valid price lists
  that were correctly being shown in the storefront's product pages.

*How*

* Adds a new workflow step that expands an optional customer_id into the
  customer_group_ids it belongs to.
* Uses this step in the addToCartWorkflow and
  updateLineItemInCartWorkflow workflows.

*Testing*
* Using medusa-dev to test on a local backend.
* Adds integration tests for the addToCart and updateLineItemInCart
  workflows.

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
  • Loading branch information
sergiocampama and riqwan authored Dec 12, 2024
1 parent 65ad05f commit ef7a6ea
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 5 deletions.
300 changes: 300 additions & 0 deletions integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
import {
ContainerRegistrationKeys,
Modules,
PriceListStatus,
PriceListType,
RuleOperator,
} from "@medusajs/utils"
import {
Expand Down Expand Up @@ -921,9 +923,307 @@ medusaIntegrationTestRunner({
},
])
})

it("should add item to cart with price list", async () => {
const salesChannel = await scModuleService.createSalesChannels({
name: "Webshop",
})

const customer = await customerModule.createCustomers({
first_name: "Test",
last_name: "Test",
})

const customer_group = await customerModule.createCustomerGroups({
name: "Test Group",
})

await customerModule.addCustomerToGroup({
customer_id: customer.id,
customer_group_id: customer_group.id,
})

const location = await stockLocationModule.createStockLocations({
name: "Warehouse",
})

let cart = await cartModuleService.createCarts({
currency_code: "usd",
sales_channel_id: salesChannel.id,
customer_id: customer.id,
})

const [product] = await productModule.createProducts([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])

const inventoryItem = await inventoryModule.createInventoryItems({
sku: "inv-1234",
})

await inventoryModule.createInventoryLevels([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
},
])

const priceSet = await pricingModule.createPriceSets({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})

await pricingModule.createPricePreferences({
attribute: "currency_code",
value: "usd",
is_tax_inclusive: true,
})

await pricingModule.createPriceLists([
{
title: "test price list",
description: "test",
status: PriceListStatus.ACTIVE,
type: PriceListType.OVERRIDE,
prices: [
{
amount: 1500,
currency_code: "usd",
price_set_id: priceSet.id,
},
],
rules: {
"customer_group_id": [customer_group.id]
}
},
])

await remoteLink.create([
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])

cart = await cartModuleService.retrieveCart(cart.id, {
select: ["id", "region_id", "currency_code", "sales_channel_id"],
})

await addToCartWorkflow(appContainer).run({
input: {
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
cart,
},
})

cart = await cartModuleService.retrieveCart(cart.id, {
relations: ["items"],
})

expect(cart).toEqual(
expect.objectContaining({
id: cart.id,
currency_code: "usd",
items: expect.arrayContaining([
expect.objectContaining({
unit_price: 1500,
is_tax_inclusive: true,
quantity: 1,
title: "Test variant",
}),
]),
})
)
})
})

describe("updateLineItemInCartWorkflow", () => {
it("should update item in cart with price list", async () => {
const salesChannel = await scModuleService.createSalesChannels({
name: "Webshop",
})

const customer = await customerModule.createCustomers({
first_name: "Test",
last_name: "Test",
})

const customer_group = await customerModule.createCustomerGroups({
name: "Test Group",
})

await customerModule.addCustomerToGroup({
customer_id: customer.id,
customer_group_id: customer_group.id,
})

const location = await stockLocationModule.createStockLocations({
name: "Warehouse",
})

const [product] = await productModule.createProducts([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])

const inventoryItem = await inventoryModule.createInventoryItems({
sku: "inv-1234",
})

await inventoryModule.createInventoryLevels([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
},
])

const priceSet = await pricingModule.createPriceSets({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})

await pricingModule.createPriceLists([
{
title: "test price list",
description: "test",
status: PriceListStatus.ACTIVE,
type: PriceListType.OVERRIDE,
prices: [
{
amount: 1500,
currency_code: "usd",
price_set_id: priceSet.id,
},
],
rules: {
"customer_group_id": [customer_group.id]
}
},
])

await remoteLink.create([
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])

let cart = await cartModuleService.createCarts({
currency_code: "usd",
sales_channel_id: salesChannel.id,
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
unit_price: 5000,
title: "Test item",
},
],
})

cart = await cartModuleService.retrieveCart(cart.id, {
select: ["id", "region_id", "currency_code"],
relations: ["items", "items.variant_id", "items.metadata"],
})

const item = cart.items?.[0]!

const { errors } = await updateLineItemInCartWorkflow(
appContainer
).run({
input: {
cart,
item,
update: {
metadata: {
foo: "bar",
},
quantity: 2,
},
},
throwOnError: false,
})

const updatedItem = await cartModuleService.retrieveLineItem(item.id)

expect(updatedItem).toEqual(
expect.objectContaining({
id: item.id,
unit_price: 1500,
quantity: 2,
title: "Test item",
})
)
})

describe("compensation", () => {
it("should revert line item update to original state", async () => {
expect.assertions(2)
Expand Down
15 changes: 11 additions & 4 deletions packages/core/core-flows/src/cart/workflows/add-to-cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import {
} from "@medusajs/framework/types"
import { CartWorkflowEvents } from "@medusajs/framework/utils"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
WorkflowData,
transform
} from "@medusajs/framework/workflows-sdk"
import { emitEventStep } from "../../common/steps/emit-event"
import { fetchCustomerGroupsStep } from "../../common/steps/fetch-customer-groups"
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
import {
createLineItemsStep,
Expand All @@ -23,6 +24,7 @@ import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
import { refreshCartItemsWorkflow } from "./refresh-cart-items"


export const addToCartWorkflowId = "add-to-cart"
/**
* This workflow adds items to a cart.
Expand All @@ -36,14 +38,19 @@ export const addToCartWorkflow = createWorkflow(
return (data.input.items ?? []).map((i) => i.variant_id)
})

const { customer_group_ids } = fetchCustomerGroupsStep(input.cart.customer_id)

// TODO: This is on par with the context used in v1.*, but we can be more flexible.
// TODO: create a common workflow to fetch variants and its prices
const pricingContext = transform({ cart: input.cart }, (data) => {
return {
const pricingContext = transform({ cart: input.cart, customer_group_ids }, (data) => {
const pricingContext = {
currency_code: data.cart.currency_code,
region_id: data.cart.region_id,
customer_id: data.cart.customer_id,
customer_group_id: data.customer_group_ids,
}
console.log(pricingContext)
return pricingContext
})

const variants = useRemoteQueryStep({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
import { fetchCustomerGroupsStep } from "../../common/steps/fetch-customer-groups"
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
import { updateLineItemsStepWithSelector } from "../../line-item/steps"
import { validateCartStep } from "../steps/validate-cart"
Expand All @@ -25,12 +26,15 @@ export const updateLineItemInCartWorkflow = createWorkflow(
return [data.input.item.variant_id]
})

const { customer_group_ids } = fetchCustomerGroupsStep(input.cart.customer_id)

// TODO: This is on par with the context used in v1.*, but we can be more flexible.
const pricingContext = transform({ cart: input.cart }, (data) => {
const pricingContext = transform({ cart: input.cart, customer_group_ids }, (data) => {
return {
currency_code: data.cart.currency_code,
region_id: data.cart.region_id,
customer_id: data.cart.customer_id,
customer_group_id: data.customer_group_ids,
}
})

Expand Down
Loading

0 comments on commit ef7a6ea

Please sign in to comment.