diff --git a/.changeset/fast-houses-invent.md b/.changeset/fast-houses-invent.md new file mode 100644 index 0000000000..53f2bc7987 --- /dev/null +++ b/.changeset/fast-houses-invent.md @@ -0,0 +1,7 @@ +--- +"@medusajs/cart": patch +"@medusajs/core-flows": patch +"@medusajs/types": patch +--- + +chore(): Improve cart update line items diff --git a/packages/core/core-flows/src/cart/steps/get-line-item-actions.ts b/packages/core/core-flows/src/cart/steps/get-line-item-actions.ts index 807de541ca..cc3d2c5d46 100644 --- a/packages/core/core-flows/src/cart/steps/get-line-item-actions.ts +++ b/packages/core/core-flows/src/cart/steps/get-line-item-actions.ts @@ -2,6 +2,7 @@ import { CartLineItemDTO, CreateLineItemForCartDTO, ICartModuleService, + UpdateLineItemWithoutSelectorDTO, UpdateLineItemWithSelectorDTO, } from "@medusajs/framework/types" import { @@ -34,7 +35,9 @@ export interface GetLineItemActionsStepOutput { /** * The line items to update. */ - itemsToUpdate: UpdateLineItemWithSelectorDTO[] + itemsToUpdate: + | UpdateLineItemWithSelectorDTO[] + | UpdateLineItemWithoutSelectorDTO[] } export const getLineItemActionsStepId = "get-line-item-actions-step" @@ -63,17 +66,29 @@ export const getLineItemActionsStep = createStep( const cartModule = container.resolve(Modules.CART) const variantIds = data.items.map((d) => d.variant_id!) - const existingVariantItems = await cartModule.listLineItems({ - cart_id: data.id, - variant_id: variantIds, - }) + const existingVariantItems = await cartModule.listLineItems( + { + cart_id: data.id, + variant_id: variantIds, + }, + { + select: [ + "id", + "metadata", + "variant_id", + "quantity", + "unit_price", + "compare_at_unit_price", + ], + } + ) const variantItemMap = new Map( existingVariantItems.map((item) => [item.variant_id!, item]) ) const itemsToCreate: CreateLineItemForCartDTO[] = [] - const itemsToUpdate: UpdateLineItemWithSelectorDTO[] = [] + const itemsToUpdate: UpdateLineItemWithSelectorDTO["data"][] = [] for (const item of data.items) { const existingItem = variantItemMap.get(item.variant_id!) @@ -88,15 +103,12 @@ export const getLineItemActionsStep = createStep( ) itemsToUpdate.push({ - selector: { id: existingItem.id }, - data: { - id: existingItem.id, - quantity: quantity, - variant_id: item.variant_id!, - unit_price: item.unit_price ?? existingItem.unit_price, - compare_at_unit_price: - item.compare_at_unit_price ?? existingItem.compare_at_unit_price, - }, + id: existingItem.id, + quantity: quantity, + variant_id: item.variant_id!, + unit_price: item.unit_price ?? existingItem.unit_price, + compare_at_unit_price: + item.compare_at_unit_price ?? existingItem.compare_at_unit_price, }) } else { itemsToCreate.push(item) diff --git a/packages/core/core-flows/src/cart/steps/update-line-items.ts b/packages/core/core-flows/src/cart/steps/update-line-items.ts index edc741b88f..7a2e6cdb69 100644 --- a/packages/core/core-flows/src/cart/steps/update-line-items.ts +++ b/packages/core/core-flows/src/cart/steps/update-line-items.ts @@ -1,5 +1,6 @@ import { ICartModuleService, + UpdateLineItemWithoutSelectorDTO, UpdateLineItemWithSelectorDTO, } from "@medusajs/framework/types" import { @@ -19,13 +20,13 @@ export interface UpdateLineItemsStepInput { /** * The line items to update. */ - items: UpdateLineItemWithSelectorDTO[] + items: (UpdateLineItemWithSelectorDTO | UpdateLineItemWithoutSelectorDTO)[] } export const updateLineItemsStepId = "update-line-items-step" /** * This step updates a cart's line items. - * + * * @example * const data = updateLineItemsStep({ * id: "cart_123", @@ -53,16 +54,18 @@ export const updateLineItemsStep = createStep( const cartModule = container.resolve(Modules.CART) const { selects, relations } = getSelectsAndRelationsFromObjectArray( - items.map((item) => item.data) + items.map((item) => ("data" in item ? item.data : item)) ) const itemsBeforeUpdate = await cartModule.listLineItems( - { id: items.map((d) => d.selector.id!) }, + { id: items.map((d) => ("selector" in d ? d.selector.id! : d.id!)) }, { select: selects, relations } ) const updatedItems = items.length - ? await cartModule.updateLineItems(items) + ? await cartModule.updateLineItems( + items as UpdateLineItemWithoutSelectorDTO[] + ) : [] return new StepResponse(updatedItems, itemsBeforeUpdate) diff --git a/packages/core/core-flows/src/cart/utils/prepare-confirm-inventory-input.ts b/packages/core/core-flows/src/cart/utils/prepare-confirm-inventory-input.ts index cb919d7c9e..2318ed5cfa 100644 --- a/packages/core/core-flows/src/cart/utils/prepare-confirm-inventory-input.ts +++ b/packages/core/core-flows/src/cart/utils/prepare-confirm-inventory-input.ts @@ -51,11 +51,13 @@ export const prepareConfirmInventoryInput = (data: { const salesChannelId = data.input.sales_channel_id for (const updateItem of data.input.itemsToUpdate ?? []) { + const updateItem_ = "data" in updateItem ? updateItem.data : updateItem + const item = data.input.items.find( - (item) => item.variant_id === updateItem.data.variant_id + (item) => item.variant_id === updateItem_.variant_id ) - if (item && updateItem.data.quantity) { - item.quantity = updateItem.data.quantity! + if (item && updateItem_.quantity) { + item.quantity = updateItem_.quantity! } } diff --git a/packages/core/core-flows/src/cart/workflows/add-to-cart.ts b/packages/core/core-flows/src/cart/workflows/add-to-cart.ts index e699f99f2e..3d566be1bd 100644 --- a/packages/core/core-flows/src/cart/workflows/add-to-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/add-to-cart.ts @@ -1,4 +1,7 @@ -import { AddToCartWorkflowInputDTO } from "@medusajs/framework/types" +import { + AddToCartWorkflowInputDTO, + ConfirmVariantInventoryWorkflowInputDTO, +} from "@medusajs/framework/types" import { CartWorkflowEvents, isDefined } from "@medusajs/framework/utils" import { createHook, @@ -141,12 +144,32 @@ export const addToCartWorkflow = createWorkflow( items: lineItems, }) + const itemsToConfirmInventory = transform( + { itemsToUpdate, itemsToCreate }, + (data) => { + return (data.itemsToUpdate as []) + .concat(data.itemsToCreate as []) + .filter( + ( + item: + | { + data: { variant_id: string } + } + | { variant_id?: string } + ) => + isDefined( + "data" in item ? item.data?.variant_id : item.variant_id + ) + ) as unknown as ConfirmVariantInventoryWorkflowInputDTO["itemsToUpdate"] + } + ) + confirmVariantInventoryWorkflow.runAsStep({ input: { sales_channel_id: cart.sales_channel_id, variants, items: input.items, - itemsToUpdate, + itemsToUpdate: itemsToConfirmInventory, }, }) diff --git a/packages/core/types/src/cart/mutations.ts b/packages/core/types/src/cart/mutations.ts index 376e19d83b..b5b52a53e4 100644 --- a/packages/core/types/src/cart/mutations.ts +++ b/packages/core/types/src/cart/mutations.ts @@ -618,6 +618,11 @@ export interface UpdateLineItemWithSelectorDTO { data: Partial } +export interface UpdateLineItemWithoutSelectorDTO + extends Omit, "id"> { + id: string +} + /** * A pair of selectors and data, where the selectors determine which * carts to update, and the data determines what to update diff --git a/packages/core/types/src/cart/service.ts b/packages/core/types/src/cart/service.ts index f9f42d41ab..9d4ac7dbd3 100644 --- a/packages/core/types/src/cart/service.ts +++ b/packages/core/types/src/cart/service.ts @@ -36,6 +36,7 @@ import { UpdateCartDTO, UpdateLineItemDTO, UpdateLineItemTaxLineDTO, + UpdateLineItemWithoutSelectorDTO, UpdateLineItemWithSelectorDTO, UpdateShippingMethodAdjustmentDTO, UpdateShippingMethodDTO, @@ -647,6 +648,25 @@ export interface ICartModuleService extends IModuleService { data: UpdateLineItemWithSelectorDTO[] ): Promise + /** + * This method updates existing line items. + * + * @param {UpdateLineItemWithoutSelectorDTO[]} data - A list of objects, each holding the data + * and id to update. + * @returns {Promise} The updated line items. + * + * @example + * const lineItems = await cartModuleService.updateLineItems([ + * { + * id: "cali_123", + * quantity: 2, + * }, + * ]) + */ + updateLineItems( + data: UpdateLineItemWithoutSelectorDTO[] + ): Promise + /** * This method updates existing line items matching the specified filters. * diff --git a/packages/core/types/src/cart/workflows.ts b/packages/core/types/src/cart/workflows.ts index 9e279d94a4..8aad005c93 100644 --- a/packages/core/types/src/cart/workflows.ts +++ b/packages/core/types/src/cart/workflows.ts @@ -106,7 +106,7 @@ export interface CreateCartCreateLineItemDTO { is_discountable?: boolean /** - * Whether the line item's price is tax inclusive. Learn more in + * Whether the line item's price is tax inclusive. Learn more in * [this documentation](https://docs.medusajs.com/resources/commerce-modules/pricing/tax-inclusive-pricing) */ is_tax_inclusive?: boolean @@ -158,54 +158,54 @@ export interface CreateCartAddressDTO { * The first name of the customer associated with the address. */ first_name?: string - + /** * The last name of the customer associated with the address. */ last_name?: string - + /** * The address's phone number */ phone?: string - + /** * The address's company name. */ company?: string - + /** * The primary address line. */ address_1?: string - + /** * The secondary address line. */ address_2?: string - + /** * The city of the address. */ city?: string - + /** * The country code of the address. - * + * * @example us */ country_code?: string - + /** * The province or state of the address. */ province?: string - + /** * The postal code of the address. */ postal_code?: string - + /** * Custom key-value pairs related to the address. */ @@ -238,7 +238,7 @@ export interface CreateCartWorkflowInputDTO { /** * The currency code of the cart. This defaults to the region's currency code. - * + * * @example usd */ currency_code?: string @@ -329,7 +329,7 @@ export interface UpdateCartWorkflowInputDTO { /** * The currency code for the cart. - * + * * @example usd */ currency_code?: string @@ -493,21 +493,32 @@ export interface ConfirmVariantInventoryWorkflowInputDTO { * The new quantity of the variant to be added to the cart. * This is useful when updating a variant's quantity in the cart. */ - itemsToUpdate?: { - /** - * The item update's details. - */ - data: { - /** - * The ID of the associated variant. - */ - variant_id?: string - /** - * The variant's quantity. - */ - quantity?: BigNumberInput - } - }[] + itemsToUpdate?: + | { + /** + * The item update's details. + */ + data: { + /** + * The ID of the associated variant. + */ + variant_id?: string + /** + * The variant's quantity. + */ + quantity?: BigNumberInput + } + }[] + | { + /** + * The ID of the associated variant. + */ + variant_id?: string + /** + * The variant's quantity. + */ + quantity?: BigNumberInput + }[] } export interface CartWorkflowDTO { diff --git a/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts b/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts index aac2f14112..0363633456 100644 --- a/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts +++ b/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts @@ -676,6 +676,34 @@ moduleIntegrationTestRunner({ expect(updatedItem.title).toBe("test2") }) + it("should update a line item in cart succesfully with data only approach", async () => { + const [createdCart] = await service.createCarts([ + { + currency_code: "eur", + }, + ]) + + const [item] = await service.addLineItems(createdCart.id, [ + { + quantity: 1, + unit_price: 100, + title: "test", + tax_lines: [], + }, + ]) + + expect(item.title).toBe("test") + + const [updatedItem] = await service.updateLineItems([ + { + id: item.id, + title: "test2", + }, + ]) + + expect(updatedItem.title).toBe("test2") + }) + it("should update a line item in cart succesfully with id approach", async () => { const [createdCart] = await service.createCarts([ { diff --git a/packages/modules/cart/src/services/cart-module.ts b/packages/modules/cart/src/services/cart-module.ts index 95a042ea6b..ebac8b3eb8 100644 --- a/packages/modules/cart/src/services/cart-module.ts +++ b/packages/modules/cart/src/services/cart-module.ts @@ -489,6 +489,12 @@ export default class CartModuleService updateLineItems( data: CartTypes.UpdateLineItemWithSelectorDTO[] ): Promise + + // @ts-ignore + updateLineItems( + data: (Partial & { id: string })[] + ): Promise + // @ts-expect-error updateLineItems( selector: Partial, @@ -508,7 +514,8 @@ export default class CartModuleService lineItemIdOrDataOrSelector: | string | CartTypes.UpdateLineItemWithSelectorDTO[] - | Partial, + | Partial + | (Partial & { id: string })[], data?: CartTypes.UpdateLineItemDTO | Partial, @MedusaContext() sharedContext: Context = {} ): Promise { @@ -521,10 +528,17 @@ export default class CartModuleService ) return await this.baseRepository_.serialize( - item, - { - populate: true, - } + item + ) + } else if (Array.isArray(lineItemIdOrDataOrSelector) && !data) { + // We received an array of data including the ids + const items = await this.lineItemService_.update( + lineItemIdOrDataOrSelector, + sharedContext + ) + + return await this.baseRepository_.serialize( + items ) } @@ -537,7 +551,10 @@ export default class CartModuleService } as CartTypes.UpdateLineItemWithSelectorDTO, ] - items = await this.updateLineItemsWithSelector_(toUpdate, sharedContext) + items = await this.updateLineItemsWithSelector_( + toUpdate as CartTypes.UpdateLineItemWithSelectorDTO[], + sharedContext + ) return await this.baseRepository_.serialize( items,