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

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

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
fix(medusa): Update cart sales channel should not remove all line items
@@ -42,6 +42,11 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
describe("POST /store/carts/:id", () => { describe("POST /store/carts/:id", () => {
let product let product
let product2
let product3
const salesChannelId = "sales-channel"
const defaultSalesChannelId = "default-sales-channel"
beforeEach(async () => { beforeEach(async () => {
await simpleRegionFactory(dbConnection, { await simpleRegionFactory(dbConnection, {
@@ -55,13 +60,13 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
product = await simpleProductFactory(dbConnection, { product = await simpleProductFactory(dbConnection, {
sales_channels: [ sales_channels: [
{ {
id: "sales-channel", id: salesChannelId,
name: "Sales channel", name: "Sales channel",
description: "Sales channel", description: "Sales channel",
is_disabled: false, is_disabled: false,
}, },
{ {
id: "default-sales-channel", id: defaultSalesChannelId,
name: "Main sales channel", name: "Main sales channel",
description: "Main sales channel", description: "Main sales channel",
is_default: true, is_default: true,
@@ -69,6 +74,10 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
}, },
], ],
}) })
product2 = await simpleProductFactory(dbConnection)
product3 = await simpleProductFactory(dbConnection)
}) })
afterEach(async () => { afterEach(async () => {
@@ -91,7 +100,7 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
quantity: 1, quantity: 1,
}, },
], ],
sales_channel_id: "sales-channel", sales_channel_id: salesChannelId,
}) })
const cart = createCartRes.data.cart const cart = createCartRes.data.cart
@@ -110,7 +119,7 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
expect(createdOrder.status).toEqual(200) expect(createdOrder.status).toEqual(200)
expect(createdOrder.data.data).toEqual( expect(createdOrder.data.data).toEqual(
expect.objectContaining({ 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])
})
}) })
}) })
@@ -744,9 +744,9 @@ describe("CartService", () => {
) )
expect(lineItemService.delete).toHaveBeenCalledTimes(1) expect(lineItemService.delete).toHaveBeenCalledTimes(1)
expect(lineItemService.delete).toHaveBeenCalledWith( expect(lineItemService.delete).toHaveBeenCalledWith([
IdMap.getId("itemToRemove") IdMap.getId("itemToRemove"),
) ])
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1) expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({ expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
@@ -267,21 +267,23 @@ const unknownVariantId = "unknown-variant"
}) })
describe("delete", () => { describe("delete", () => {
const lineItemRepository = MockRepository({ const lineItemRepository = MockRepository({
findOne: () => find: () =>
Promise.resolve({ Promise.resolve([
id: IdMap.getId("test-line-item"), {
variant_id: IdMap.getId("test-variant"), id: IdMap.getId("test-line-item"),
variant: { variant_id: IdMap.getId("test-variant"),
id: IdMap.getId("test-variant"), variant: {
title: "Test 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({ const lineItemService = new LineItemService({
@@ -297,20 +299,22 @@ const unknownVariantId = "unknown-variant"
await lineItemService.delete(IdMap.getId("test-line-item")) await lineItemService.delete(IdMap.getId("test-line-item"))
expect(lineItemRepository.remove).toHaveBeenCalledTimes(1) expect(lineItemRepository.remove).toHaveBeenCalledTimes(1)
expect(lineItemRepository.remove).toHaveBeenCalledWith({ expect(lineItemRepository.remove).toHaveBeenCalledWith([
id: IdMap.getId("test-line-item"), {
variant_id: IdMap.getId("test-variant"), id: IdMap.getId("test-line-item"),
variant: { variant_id: IdMap.getId("test-variant"),
id: IdMap.getId("test-variant"), variant: {
title: "Test 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,
})
}) })
}) })
}) })
+23 -21
View File
@@ -560,23 +560,29 @@ class CartService extends TransactionBaseService {
/** /**
* Removes a line item from the cart. * Removes a line item from the cart.
* @param cartId - the id of the cart that we will remove from * @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 * @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_( return await this.atomicPhase_(
async (transactionManager: EntityManager) => { async (transactionManager: EntityManager) => {
const cart = await this.retrieve(cartId, { const cart = await this.retrieve(cartId, {
relations: [ relations: ["items.variant.product.profiles", "shipping_methods"],
"items.variant.product.profiles",
"payment_sessions",
"shipping_methods",
],
}) })
const lineItem = cart.items.find((item) => item.id === lineItemId) const lineItemIdsToRemove = new Set(
if (!lineItem) { Array.isArray(lineItemId) ? lineItemId : [lineItemId]
return cart )
const lineItems = cart.items.filter((item) =>
lineItemIdsToRemove.has(item.id)
)
if (!lineItems.length) {
return
} }
if (cart.shipping_methods?.length) { if (cart.shipping_methods?.length) {
@@ -599,12 +605,11 @@ class CartService extends TransactionBaseService {
await this.lineItemService_ await this.lineItemService_
.withTransaction(transactionManager) .withTransaction(transactionManager)
.delete(lineItem.id) .delete([...lineItemIdsToRemove])
const result = await this.retrieve(cartId, { const result = await this.retrieve(cartId, {
relations: [ relations: [
"items.variant.product.profiles", "items.variant.product.profiles",
"discounts",
"discounts.rule", "discounts.rule",
"region", "region",
], ],
@@ -618,8 +623,6 @@ class CartService extends TransactionBaseService {
.emit(CartService.Events.UPDATED, { .emit(CartService.Events.UPDATED, {
id: cart.id, id: cart.id,
}) })
return this.retrieve(cartId)
} }
) )
} }
@@ -1423,14 +1426,13 @@ class CartService extends TransactionBaseService {
return !productIdsToKeep.has(item.variant.product_id) return !productIdsToKeep.has(item.variant.product_id)
}) })
if (itemsToRemove.length) { if (!itemsToRemove.length) {
const results = await promiseAll( return
itemsToRemove.map(async (item) => {
return this.removeLineItem(cart.id, item.id)
})
)
cart.items = results.pop()?.items ?? []
} }
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))
} }
/** /**
+30 -21
View File
@@ -1,28 +1,27 @@
import {MedusaError} from "medusa-core-utils" import { MedusaError } from "medusa-core-utils"
import {EntityManager, In} from "typeorm" import { EntityManager, In } from "typeorm"
import {DeepPartial} from "typeorm/common/DeepPartial" import { DeepPartial } from "typeorm/common/DeepPartial"
import { import {
FlagRouter, FlagRouter,
MedusaV2Flag, MedusaV2Flag,
selectorConstraintsToString selectorConstraintsToString,
} from "@medusajs/utils" } from "@medusajs/utils"
import {TransactionBaseService} from "../interfaces" import { TransactionBaseService } from "../interfaces"
import TaxInclusivePricingFeatureFlag import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
from "../loaders/feature-flags/tax-inclusive-pricing"
import { import {
LineItem, LineItem,
LineItemAdjustment, LineItemAdjustment,
LineItemTaxLine, LineItemTaxLine,
ProductVariant, ProductVariant,
} from "../models" } from "../models"
import {CartRepository} from "../repositories/cart" import { CartRepository } from "../repositories/cart"
import {LineItemRepository} from "../repositories/line-item" import { LineItemRepository } from "../repositories/line-item"
import {LineItemTaxLineRepository} from "../repositories/line-item-tax-line" import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
import {FindConfig, Selector} from "../types/common" import { FindConfig, Selector } from "../types/common"
import {GenerateInputData, GenerateLineItemContext} from "../types/line-item" import { GenerateInputData, GenerateLineItemContext } from "../types/line-item"
import {ProductVariantPricing} from "../types/pricing" import { ProductVariantPricing } from "../types/pricing"
import {buildQuery, isString, setMetadata} from "../utils" import { buildQuery, isString, setMetadata } from "../utils"
import { import {
PricingService, PricingService,
ProductService, 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. * Deletes a line item.
* @param id - the id of the line item to delete * @param id - the id of the line item to delete
* @return the result of the delete operation * @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_( return await this.atomicPhase_(
async (transactionManager: EntityManager) => { async (transactionManager: EntityManager) => {
const lineItemRepository = transactionManager.withRepository( const lineItemRepository = transactionManager.withRepository(
this.lineItemRepository_ this.lineItemRepository_
) )
return await lineItemRepository const lineItems = await lineItemRepository.find({
.findOne({ where: { id } }) where: { id: In(ids) },
.then( })
async (lineItem) => lineItem && lineItemRepository.remove(lineItem)
) 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 * @param id - the id of the line item to delete
* @return the result of the delete operation * @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_( return await this.atomicPhase_(
async (transactionManager: EntityManager) => { async (transactionManager: EntityManager) => {
await this.taxProviderService_ await this.taxProviderService_