fix(medusa): Update cart sales channel should not remove all line items (#5980)

This commit is contained in:
Adrien de Peretti
2024-01-03 14:06:07 +01:00
committed by GitHub
parent 42cc8ae3f8
commit 278b7fb203
6 changed files with 140 additions and 76 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
fix(medusa): Update cart sales channel should not remove all line items

View File

@@ -42,6 +42,11 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
describe("POST /store/carts/:id", () => {
let product
let product2
let product3
const salesChannelId = "sales-channel"
const defaultSalesChannelId = "default-sales-channel"
beforeEach(async () => {
await simpleRegionFactory(dbConnection, {
@@ -55,13 +60,13 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
product = await simpleProductFactory(dbConnection, {
sales_channels: [
{
id: "sales-channel",
id: salesChannelId,
name: "Sales channel",
description: "Sales channel",
is_disabled: false,
},
{
id: "default-sales-channel",
id: defaultSalesChannelId,
name: "Main sales channel",
description: "Main sales channel",
is_default: true,
@@ -69,6 +74,10 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
},
],
})
product2 = await simpleProductFactory(dbConnection)
product3 = await simpleProductFactory(dbConnection)
})
afterEach(async () => {
@@ -91,7 +100,7 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
quantity: 1,
},
],
sales_channel_id: "sales-channel",
sales_channel_id: salesChannelId,
})
const cart = createCartRes.data.cart
@@ -110,7 +119,7 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
expect(createdOrder.status).toEqual(200)
expect(createdOrder.data.data).toEqual(
expect.objectContaining({
sales_channel_id: "sales-channel",
sales_channel_id: salesChannelId,
})
)
})
@@ -152,5 +161,40 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
})
)
})
it("should remove the line items that does not belong to the new sales channel", async () => {
const api = useApi()
let createCartRes = await api.post("/store/carts", {
region_id: "test-region",
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
{
variant_id: product2.variants[0].id,
quantity: 1,
},
{
variant_id: product3.variants[0].id,
quantity: 1,
},
],
sales_channel_id: defaultSalesChannelId,
})
let items = createCartRes.data.cart.items
expect(items).toHaveLength(3)
const cartId = createCartRes.data.cart.id
createCartRes = await api.post(`/store/carts/${cartId}`, {
sales_channel_id: salesChannelId,
})
items = createCartRes.data.cart.items
expect(items).toHaveLength(1)
expect(items.map((i) => i.variant.id)).toEqual([product.variants[0].id])
})
})
})

View File

@@ -744,9 +744,9 @@ describe("CartService", () => {
)
expect(lineItemService.delete).toHaveBeenCalledTimes(1)
expect(lineItemService.delete).toHaveBeenCalledWith(
IdMap.getId("itemToRemove")
)
expect(lineItemService.delete).toHaveBeenCalledWith([
IdMap.getId("itemToRemove"),
])
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({

View File

@@ -267,21 +267,23 @@ const unknownVariantId = "unknown-variant"
})
describe("delete", () => {
const lineItemRepository = MockRepository({
findOne: () =>
Promise.resolve({
id: IdMap.getId("test-line-item"),
variant_id: IdMap.getId("test-variant"),
variant: {
id: IdMap.getId("test-variant"),
title: "Test variant",
find: () =>
Promise.resolve([
{
id: IdMap.getId("test-line-item"),
variant_id: IdMap.getId("test-variant"),
variant: {
id: IdMap.getId("test-variant"),
title: "Test variant",
},
cart_id: IdMap.getId("test-cart"),
title: "Test product",
description: "Test variant",
thumbnail: "",
unit_price: 50,
quantity: 1,
},
cart_id: IdMap.getId("test-cart"),
title: "Test product",
description: "Test variant",
thumbnail: "",
unit_price: 50,
quantity: 1,
}),
]),
})
const lineItemService = new LineItemService({
@@ -297,20 +299,22 @@ const unknownVariantId = "unknown-variant"
await lineItemService.delete(IdMap.getId("test-line-item"))
expect(lineItemRepository.remove).toHaveBeenCalledTimes(1)
expect(lineItemRepository.remove).toHaveBeenCalledWith({
id: IdMap.getId("test-line-item"),
variant_id: IdMap.getId("test-variant"),
variant: {
id: IdMap.getId("test-variant"),
title: "Test variant",
expect(lineItemRepository.remove).toHaveBeenCalledWith([
{
id: IdMap.getId("test-line-item"),
variant_id: IdMap.getId("test-variant"),
variant: {
id: IdMap.getId("test-variant"),
title: "Test variant",
},
cart_id: IdMap.getId("test-cart"),
title: "Test product",
description: "Test variant",
thumbnail: "",
unit_price: 50,
quantity: 1,
},
cart_id: IdMap.getId("test-cart"),
title: "Test product",
description: "Test variant",
thumbnail: "",
unit_price: 50,
quantity: 1,
})
])
})
})
})

View File

@@ -560,23 +560,29 @@ class CartService extends TransactionBaseService {
/**
* Removes a line item from the cart.
* @param cartId - the id of the cart that we will remove from
* @param lineItemId - the line item to remove.
* @param lineItemId - the line item(s) to remove.
* @return the result of the update operation
*/
async removeLineItem(cartId: string, lineItemId: string): Promise<Cart> {
async removeLineItem(
cartId: string,
lineItemId: string | string[]
): Promise<void> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const cart = await this.retrieve(cartId, {
relations: [
"items.variant.product.profiles",
"payment_sessions",
"shipping_methods",
],
relations: ["items.variant.product.profiles", "shipping_methods"],
})
const lineItem = cart.items.find((item) => item.id === lineItemId)
if (!lineItem) {
return cart
const lineItemIdsToRemove = new Set(
Array.isArray(lineItemId) ? lineItemId : [lineItemId]
)
const lineItems = cart.items.filter((item) =>
lineItemIdsToRemove.has(item.id)
)
if (!lineItems.length) {
return
}
if (cart.shipping_methods?.length) {
@@ -599,12 +605,11 @@ class CartService extends TransactionBaseService {
await this.lineItemService_
.withTransaction(transactionManager)
.delete(lineItem.id)
.delete([...lineItemIdsToRemove])
const result = await this.retrieve(cartId, {
relations: [
"items.variant.product.profiles",
"discounts",
"discounts.rule",
"region",
],
@@ -618,8 +623,6 @@ class CartService extends TransactionBaseService {
.emit(CartService.Events.UPDATED, {
id: cart.id,
})
return this.retrieve(cartId)
}
)
}
@@ -1423,14 +1426,13 @@ class CartService extends TransactionBaseService {
return !productIdsToKeep.has(item.variant.product_id)
})
if (itemsToRemove.length) {
const results = await promiseAll(
itemsToRemove.map(async (item) => {
return this.removeLineItem(cart.id, item.id)
})
)
cart.items = results.pop()?.items ?? []
if (!itemsToRemove.length) {
return
}
const itemIdsToRemove = new Set(itemsToRemove.map((item) => item.id))
await this.removeLineItem(cart.id, [...itemIdsToRemove])
cart.items = cart.items.filter((item) => !itemIdsToRemove.has(item.id))
}
/**

View File

@@ -1,28 +1,27 @@
import {MedusaError} from "medusa-core-utils"
import {EntityManager, In} from "typeorm"
import {DeepPartial} from "typeorm/common/DeepPartial"
import { MedusaError } from "medusa-core-utils"
import { EntityManager, In } from "typeorm"
import { DeepPartial } from "typeorm/common/DeepPartial"
import {
FlagRouter,
MedusaV2Flag,
selectorConstraintsToString
selectorConstraintsToString,
} from "@medusajs/utils"
import {TransactionBaseService} from "../interfaces"
import TaxInclusivePricingFeatureFlag
from "../loaders/feature-flags/tax-inclusive-pricing"
import { TransactionBaseService } from "../interfaces"
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
import {
LineItem,
LineItemAdjustment,
LineItemTaxLine,
ProductVariant,
} from "../models"
import {CartRepository} from "../repositories/cart"
import {LineItemRepository} from "../repositories/line-item"
import {LineItemTaxLineRepository} from "../repositories/line-item-tax-line"
import {FindConfig, Selector} from "../types/common"
import {GenerateInputData, GenerateLineItemContext} from "../types/line-item"
import {ProductVariantPricing} from "../types/pricing"
import {buildQuery, isString, setMetadata} from "../utils"
import { CartRepository } from "../repositories/cart"
import { LineItemRepository } from "../repositories/line-item"
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
import { FindConfig, Selector } from "../types/common"
import { GenerateInputData, GenerateLineItemContext } from "../types/line-item"
import { ProductVariantPricing } from "../types/pricing"
import { buildQuery, isString, setMetadata } from "../utils"
import {
PricingService,
ProductService,
@@ -484,23 +483,33 @@ class LineItemService extends TransactionBaseService {
)
}
async delete(ids: string[]): Promise<LineItem[]>
async delete(id: string): Promise<LineItem | void>
/**
* Deletes a line item.
* @param id - the id of the line item to delete
* @return the result of the delete operation
*/
async delete(id: string): Promise<LineItem | undefined | null> {
async delete(id: string | string[]): Promise<LineItem[] | LineItem | void> {
const ids = Array.isArray(id) ? id : [id]
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const lineItemRepository = transactionManager.withRepository(
this.lineItemRepository_
)
return await lineItemRepository
.findOne({ where: { id } })
.then(
async (lineItem) => lineItem && lineItemRepository.remove(lineItem)
)
const lineItems = await lineItemRepository.find({
where: { id: In(ids) },
})
if (!lineItems?.length) {
return Array.isArray(id) ? [] : void 0
}
const removedItems = await lineItemRepository.remove(lineItems)
return Array.isArray(id) ? removedItems : removedItems[0]
}
)
}
@@ -511,7 +520,7 @@ class LineItemService extends TransactionBaseService {
* @param id - the id of the line item to delete
* @return the result of the delete operation
*/
async deleteWithTaxLines(id: string): Promise<LineItem | undefined | null> {
async deleteWithTaxLines(id: string): Promise<LineItem | void> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
await this.taxProviderService_