From 278b7fb203f505ce163efe38055e9f36388985ea Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Wed, 3 Jan 2024 14:06:07 +0100 Subject: [PATCH] fix(medusa): Update cart sales channel should not remove all line items (#5980) --- .changeset/rude-boats-ring.md | 5 ++ .../__tests__/store/cart/ff-sales-channels.js | 52 +++++++++++++++-- .../medusa/src/services/__tests__/cart.js | 6 +- .../src/services/__tests__/line-item.js | 58 ++++++++++--------- packages/medusa/src/services/cart.ts | 44 +++++++------- packages/medusa/src/services/line-item.ts | 51 +++++++++------- 6 files changed, 140 insertions(+), 76 deletions(-) create mode 100644 .changeset/rude-boats-ring.md diff --git a/.changeset/rude-boats-ring.md b/.changeset/rude-boats-ring.md new file mode 100644 index 0000000000..a2330c8be3 --- /dev/null +++ b/.changeset/rude-boats-ring.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): Update cart sales channel should not remove all line items diff --git a/integration-tests/api/__tests__/store/cart/ff-sales-channels.js b/integration-tests/api/__tests__/store/cart/ff-sales-channels.js index 0fbf62d56d..3ee68e6e5c 100644 --- a/integration-tests/api/__tests__/store/cart/ff-sales-channels.js +++ b/integration-tests/api/__tests__/store/cart/ff-sales-channels.js @@ -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]) + }) }) }) diff --git a/packages/medusa/src/services/__tests__/cart.js b/packages/medusa/src/services/__tests__/cart.js index e0438788a6..3026ae7953 100644 --- a/packages/medusa/src/services/__tests__/cart.js +++ b/packages/medusa/src/services/__tests__/cart.js @@ -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({ diff --git a/packages/medusa/src/services/__tests__/line-item.js b/packages/medusa/src/services/__tests__/line-item.js index 57efa430a1..f2737cd03f 100644 --- a/packages/medusa/src/services/__tests__/line-item.js +++ b/packages/medusa/src/services/__tests__/line-item.js @@ -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, - }) + ]) }) }) }) diff --git a/packages/medusa/src/services/cart.ts b/packages/medusa/src/services/cart.ts index 7fd82f1445..25e7bf30a1 100644 --- a/packages/medusa/src/services/cart.ts +++ b/packages/medusa/src/services/cart.ts @@ -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 { + async removeLineItem( + cartId: string, + lineItemId: string | string[] + ): Promise { 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)) } /** diff --git a/packages/medusa/src/services/line-item.ts b/packages/medusa/src/services/line-item.ts index cb89ec787e..d70fb6d101 100644 --- a/packages/medusa/src/services/line-item.ts +++ b/packages/medusa/src/services/line-item.ts @@ -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 + async delete(id: string): Promise + /** * 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 { + async delete(id: string | string[]): Promise { + 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 { + async deleteWithTaxLines(id: string): Promise { return await this.atomicPhase_( async (transactionManager: EntityManager) => { await this.taxProviderService_