Fix/pricing update fixes (#5275)
* fix for merging line items and quantity prices * add changeset * fix draft order unit_price calculation * update test to reflect only quantity change * fix unit tests * update conditional
This commit is contained in:
5
.changeset/many-peas-draw.md
Normal file
5
.changeset/many-peas-draw.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
fix(medusa): update unit_price for line-items with quantity pricing when merging on create
|
||||
@@ -950,6 +950,41 @@ describe("/admin/draft-orders", () => {
|
||||
expect(updatedDraftOrder.data.draft_order.cart.subtotal).not.toEqual(0)
|
||||
})
|
||||
|
||||
it("updates a line item on the draft order with quantity", async () => {
|
||||
const api = useApi()
|
||||
await api.post(
|
||||
"/admin/draft-orders/test-draft-order/line-items/test-item",
|
||||
{
|
||||
unit_price: 1000,
|
||||
},
|
||||
adminReqConfig
|
||||
)
|
||||
|
||||
const response = await api.post(
|
||||
"/admin/draft-orders/test-draft-order/line-items/test-item",
|
||||
{
|
||||
quantity: 2,
|
||||
},
|
||||
adminReqConfig
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const updatedDraftOrder = await api.get(
|
||||
`/admin/draft-orders/test-draft-order`,
|
||||
adminReqConfig
|
||||
)
|
||||
|
||||
const item = updatedDraftOrder.data.draft_order.cart.items[0]
|
||||
|
||||
expect(item.unit_price).toEqual(1000)
|
||||
expect(item.quantity).toEqual(2)
|
||||
expect(updatedDraftOrder.data.draft_order.cart.subtotal).not.toEqual(
|
||||
undefined
|
||||
)
|
||||
expect(updatedDraftOrder.data.draft_order.cart.subtotal).not.toEqual(0)
|
||||
})
|
||||
|
||||
it("removes the line item, if quantity is 0", async () => {
|
||||
const api = useApi()
|
||||
|
||||
|
||||
@@ -1017,6 +1017,44 @@ describe("/store/carts", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("updates line item quantity with unit price reflected when merging line-items", async () => {
|
||||
const api = useApi()
|
||||
|
||||
await simplePriceListFactory(dbConnection, {
|
||||
id: "pl_current",
|
||||
prices: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
amount: 10,
|
||||
min_quantity: 5,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-3/line-items",
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 4,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch(console.log)
|
||||
|
||||
expect(response.data.cart.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-3",
|
||||
unit_price: 10,
|
||||
variant_id: "test-variant",
|
||||
quantity: 5,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("creates and updates line item quantity with unit price reflected", async () => {
|
||||
const api = useApi()
|
||||
|
||||
|
||||
@@ -987,6 +987,8 @@ module.exports = async (dataSource, data = {}) => {
|
||||
variant_id: "test-variant",
|
||||
product_id: "test-product",
|
||||
cart_id: "test-cart-3",
|
||||
should_merge: true,
|
||||
metadata: {},
|
||||
})
|
||||
await manager.save(li2)
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ module.exports = async (dataSource, data = {}) => {
|
||||
id: "test-variant",
|
||||
title: "test variant",
|
||||
product_id: "test-product",
|
||||
inventory_quantity: 1,
|
||||
inventory_quantity: 2,
|
||||
options: [
|
||||
{
|
||||
option_id: "test-option",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||
|
||||
describe("POST /store/carts/:id/line-items/:line_id", () => {
|
||||
describe("successfully updates a line item", () => {
|
||||
@@ -34,6 +34,7 @@ describe("POST /store/carts/:id/line-items/:line_id", () => {
|
||||
region_id: IdMap.getId("region-france"),
|
||||
quantity: 3,
|
||||
metadata: {},
|
||||
should_calculate_prices: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -126,6 +127,7 @@ describe("POST /store/carts/:id/line-items/:line_id", () => {
|
||||
quantity: 3,
|
||||
region_id: expect.any(String),
|
||||
variant_id: expect.any(String),
|
||||
should_calculate_prices: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -170,6 +172,7 @@ describe("POST /store/carts/:id/line-items/:line_id", () => {
|
||||
quantity: 3,
|
||||
region_id: expect.any(String),
|
||||
variant_id: expect.any(String),
|
||||
should_calculate_prices: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -215,6 +218,7 @@ describe("POST /store/carts/:id/line-items/:line_id", () => {
|
||||
quantity: 3,
|
||||
region_id: expect.any(String),
|
||||
variant_id: expect.any(String),
|
||||
should_calculate_prices: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { IsInt, IsOptional } from "class-validator"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||
|
||||
import { CartService } from "../../../../services"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||
import { handleAddOrUpdateLineItem } from "./create-line-item/utils/handler-steps"
|
||||
|
||||
@@ -91,6 +92,7 @@ export default async (req, res) => {
|
||||
region_id: cart.region_id,
|
||||
quantity: validated.quantity,
|
||||
metadata: validated.metadata || {},
|
||||
should_calculate_prices: true,
|
||||
}
|
||||
|
||||
await cartService
|
||||
|
||||
@@ -1,10 +1,34 @@
|
||||
import { FlagRouter } from "@medusajs/utils"
|
||||
import { isEmpty, isEqual } from "lodash"
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import { DeepPartial, EntityManager, In, IsNull, Not } from "typeorm"
|
||||
import {
|
||||
CustomerService,
|
||||
Address,
|
||||
Cart,
|
||||
CustomShippingOption,
|
||||
Customer,
|
||||
Discount,
|
||||
DiscountRule,
|
||||
DiscountRuleType,
|
||||
LineItem,
|
||||
PaymentSession,
|
||||
PaymentSessionStatus,
|
||||
SalesChannel,
|
||||
ShippingMethod,
|
||||
} from "../models"
|
||||
import {
|
||||
AddressPayload,
|
||||
FindConfig,
|
||||
TotalField,
|
||||
WithRequiredProperty,
|
||||
} from "../types/common"
|
||||
import {
|
||||
CartCreateProps,
|
||||
CartUpdateProps,
|
||||
FilterableCartProps,
|
||||
LineItemUpdate,
|
||||
LineItemValidateData,
|
||||
isCart,
|
||||
} from "../types/cart"
|
||||
import {
|
||||
CustomShippingOptionService,
|
||||
CustomerService,
|
||||
DiscountService,
|
||||
EventBusService,
|
||||
GiftCardService,
|
||||
@@ -24,46 +48,23 @@ import {
|
||||
TaxProviderService,
|
||||
TotalsService,
|
||||
} from "."
|
||||
import { DeepPartial, EntityManager, In, IsNull, Not } from "typeorm"
|
||||
import { IPriceSelectionStrategy, TransactionBaseService } from "../interfaces"
|
||||
import IsolateProductDomainFeatureFlag from "../loaders/feature-flags/isolate-product-domain"
|
||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
||||
import {
|
||||
Address,
|
||||
Cart,
|
||||
Customer,
|
||||
CustomShippingOption,
|
||||
Discount,
|
||||
DiscountRule,
|
||||
DiscountRuleType,
|
||||
LineItem,
|
||||
PaymentSession,
|
||||
PaymentSessionStatus,
|
||||
SalesChannel,
|
||||
ShippingMethod,
|
||||
} from "../models"
|
||||
import { MedusaError, isDefined } from "medusa-core-utils"
|
||||
import { buildQuery, isString, setMetadata } from "../utils"
|
||||
import { isEmpty, isEqual } from "lodash"
|
||||
|
||||
import { AddressRepository } from "../repositories/address"
|
||||
import { CartRepository } from "../repositories/cart"
|
||||
import { LineItemRepository } from "../repositories/line-item"
|
||||
import { PaymentSessionRepository } from "../repositories/payment-session"
|
||||
import { ShippingMethodRepository } from "../repositories/shipping-method"
|
||||
import {
|
||||
CartCreateProps,
|
||||
CartUpdateProps,
|
||||
FilterableCartProps,
|
||||
isCart,
|
||||
LineItemUpdate,
|
||||
LineItemValidateData,
|
||||
} from "../types/cart"
|
||||
import {
|
||||
AddressPayload,
|
||||
FindConfig,
|
||||
TotalField,
|
||||
WithRequiredProperty,
|
||||
} from "../types/common"
|
||||
import { PaymentSessionInput } from "../types/payment"
|
||||
import { buildQuery, isString, setMetadata } from "../utils"
|
||||
import { validateEmail } from "../utils/is-email"
|
||||
import { FlagRouter } from "@medusajs/utils"
|
||||
import { IsNumber } from "class-validator"
|
||||
import IsolateProductDomainFeatureFlag from "../loaders/feature-flags/isolate-product-domain"
|
||||
import { LineItemRepository } from "../repositories/line-item"
|
||||
import { PaymentSessionInput } from "../types/payment"
|
||||
import { PaymentSessionRepository } from "../repositories/payment-session"
|
||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
||||
import { ShippingMethodRepository } from "../repositories/shipping-method"
|
||||
import { validateEmail } from "../utils/is-email"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
@@ -797,7 +798,7 @@ class CartService extends TransactionBaseService {
|
||||
): Promise<void> {
|
||||
const items: LineItem[] = Array.isArray(lineItems) ? lineItems : [lineItems]
|
||||
|
||||
const select: (keyof Cart)[] = ["id"]
|
||||
const select: (keyof Cart)[] = ["id", "customer_id", "region_id"]
|
||||
|
||||
if (this.featureFlagRouter_.isFeatureEnabled("sales_channels")) {
|
||||
select.push("sales_channel_id")
|
||||
@@ -900,10 +901,33 @@ class CartService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
if (currentItem) {
|
||||
const variantsPricing = await this.pricingService_
|
||||
.withTransaction(transactionManager)
|
||||
.getProductVariantsPricing(
|
||||
[
|
||||
{
|
||||
variantId: item.variant_id!,
|
||||
quantity: item.quantity,
|
||||
},
|
||||
],
|
||||
{
|
||||
region_id: cart.region_id,
|
||||
customer_id: cart.customer_id,
|
||||
include_discount_prices: true,
|
||||
}
|
||||
)
|
||||
|
||||
const { calculated_price } =
|
||||
variantsPricing[currentItem.variant_id!]
|
||||
|
||||
lineItemsToUpdate[currentItem.id] = {
|
||||
quantity: item.quantity,
|
||||
has_shipping: false,
|
||||
}
|
||||
|
||||
if (isDefined(calculated_price)) {
|
||||
lineItemsToUpdate[currentItem.id].unit_price = calculated_price
|
||||
}
|
||||
} else {
|
||||
// Since the variant is eager loaded, we are removing it before the line item is being created.
|
||||
delete (item as Partial<LineItem>).variant
|
||||
@@ -977,8 +1001,9 @@ class CartService extends TransactionBaseService {
|
||||
async updateLineItem(
|
||||
cartId: string,
|
||||
lineItemId: string,
|
||||
lineItemUpdate: LineItemUpdate
|
||||
update: LineItemUpdate
|
||||
): Promise<Cart> {
|
||||
const { should_calculate_prices, ...lineItemUpdate } = update
|
||||
return await this.atomicPhase_(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const select: (keyof Cart)[] = ["id", "region_id", "customer_id"]
|
||||
@@ -1021,24 +1046,26 @@ class CartService extends TransactionBaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const variantsPricing = await this.pricingService_
|
||||
.withTransaction(transactionManager)
|
||||
.getProductVariantsPricing(
|
||||
[
|
||||
if (should_calculate_prices) {
|
||||
const variantsPricing = await this.pricingService_
|
||||
.withTransaction(transactionManager)
|
||||
.getProductVariantsPricing(
|
||||
[
|
||||
{
|
||||
variantId: lineItem.variant_id,
|
||||
quantity: lineItemUpdate.quantity,
|
||||
},
|
||||
],
|
||||
{
|
||||
variantId: lineItem.variant_id,
|
||||
quantity: lineItemUpdate.quantity,
|
||||
},
|
||||
],
|
||||
{
|
||||
region_id: cart.region_id,
|
||||
customer_id: cart.customer_id,
|
||||
include_discount_prices: true,
|
||||
}
|
||||
)
|
||||
region_id: cart.region_id,
|
||||
customer_id: cart.customer_id,
|
||||
include_discount_prices: true,
|
||||
}
|
||||
)
|
||||
|
||||
const { calculated_price } = variantsPricing[lineItem.variant_id]
|
||||
lineItemUpdate.unit_price = calculated_price ?? undefined
|
||||
const { calculated_price } = variantsPricing[lineItem.variant_id]
|
||||
lineItemUpdate.unit_price = calculated_price ?? undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { ValidateNested } from "class-validator"
|
||||
import { IsType } from "../utils/validators/is-type"
|
||||
import {
|
||||
AddressPayload,
|
||||
DateComparisonOperator,
|
||||
StringComparisonOperator,
|
||||
} from "./common"
|
||||
import { Cart, CartType } from "../models/cart"
|
||||
import { AddressPayload, DateComparisonOperator, StringComparisonOperator } from "./common"
|
||||
|
||||
import { IsType } from "../utils/validators/is-type"
|
||||
import { Region } from "../models"
|
||||
import { ValidateNested } from "class-validator"
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function isCart(object: any): object is Cart {
|
||||
@@ -29,10 +34,11 @@ export type LineItemUpdate = {
|
||||
metadata?: Record<string, unknown>
|
||||
region_id?: string
|
||||
variant_id?: string
|
||||
should_calculate_prices?: boolean
|
||||
}
|
||||
|
||||
export type LineItemValidateData = {
|
||||
variant?: { product_id: string };
|
||||
variant?: { product_id: string }
|
||||
variant_id: string
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user