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:
1 parent
c5703a4765
commit
90e24c593f
9 files changed
+186
-67
No files matched your search
@@ -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)
|
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 () => {
|
it("removes the line item, if quantity is 0", async () => {
|
||||||
const api = useApi()
|
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 () => {
|
it("creates and updates line item quantity with unit price reflected", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
|||||||
@@ -987,6 +987,8 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
variant_id: "test-variant",
|
variant_id: "test-variant",
|
||||||
product_id: "test-product",
|
product_id: "test-product",
|
||||||
cart_id: "test-cart-3",
|
cart_id: "test-cart-3",
|
||||||
|
should_merge: true,
|
||||||
|
metadata: {},
|
||||||
})
|
})
|
||||||
await manager.save(li2)
|
await manager.save(li2)
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
id: "test-variant",
|
id: "test-variant",
|
||||||
title: "test variant",
|
title: "test variant",
|
||||||
product_id: "test-product",
|
product_id: "test-product",
|
||||||
inventory_quantity: 1,
|
inventory_quantity: 2,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
option_id: "test-option",
|
option_id: "test-option",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||||
import { IdMap } from "medusa-test-utils"
|
import { IdMap } from "medusa-test-utils"
|
||||||
import { request } from "../../../../../helpers/test-request"
|
import { request } from "../../../../../helpers/test-request"
|
||||||
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
|
||||||
|
|
||||||
describe("POST /store/carts/:id/line-items/:line_id", () => {
|
describe("POST /store/carts/:id/line-items/:line_id", () => {
|
||||||
describe("successfully updates a line item", () => {
|
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"),
|
region_id: IdMap.getId("region-france"),
|
||||||
quantity: 3,
|
quantity: 3,
|
||||||
metadata: {},
|
metadata: {},
|
||||||
|
should_calculate_prices: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -126,6 +127,7 @@ describe("POST /store/carts/:id/line-items/:line_id", () => {
|
|||||||
quantity: 3,
|
quantity: 3,
|
||||||
region_id: expect.any(String),
|
region_id: expect.any(String),
|
||||||
variant_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,
|
quantity: 3,
|
||||||
region_id: expect.any(String),
|
region_id: expect.any(String),
|
||||||
variant_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,
|
quantity: 3,
|
||||||
region_id: expect.any(String),
|
region_id: expect.any(String),
|
||||||
variant_id: expect.any(String),
|
variant_id: expect.any(String),
|
||||||
|
should_calculate_prices: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { IsInt, IsOptional } from "class-validator"
|
import { IsInt, IsOptional } from "class-validator"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { EntityManager } from "typeorm"
|
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
|
|
||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
import { handleAddOrUpdateLineItem } from "./create-line-item/utils/handler-steps"
|
import { handleAddOrUpdateLineItem } from "./create-line-item/utils/handler-steps"
|
||||||
|
|
||||||
@@ -91,6 +92,7 @@ export default async (req, res) => {
|
|||||||
region_id: cart.region_id,
|
region_id: cart.region_id,
|
||||||
quantity: validated.quantity,
|
quantity: validated.quantity,
|
||||||
metadata: validated.metadata || {},
|
metadata: validated.metadata || {},
|
||||||
|
should_calculate_prices: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
await cartService
|
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 {
|
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,
|
CustomShippingOptionService,
|
||||||
|
CustomerService,
|
||||||
DiscountService,
|
DiscountService,
|
||||||
EventBusService,
|
EventBusService,
|
||||||
GiftCardService,
|
GiftCardService,
|
||||||
@@ -24,46 +48,23 @@ import {
|
|||||||
TaxProviderService,
|
TaxProviderService,
|
||||||
TotalsService,
|
TotalsService,
|
||||||
} from "."
|
} from "."
|
||||||
|
import { DeepPartial, EntityManager, In, IsNull, Not } from "typeorm"
|
||||||
import { IPriceSelectionStrategy, TransactionBaseService } from "../interfaces"
|
import { IPriceSelectionStrategy, TransactionBaseService } from "../interfaces"
|
||||||
import IsolateProductDomainFeatureFlag from "../loaders/feature-flags/isolate-product-domain"
|
import { MedusaError, isDefined } from "medusa-core-utils"
|
||||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
import { buildQuery, isString, setMetadata } from "../utils"
|
||||||
import {
|
import { isEmpty, isEqual } from "lodash"
|
||||||
Address,
|
|
||||||
Cart,
|
|
||||||
Customer,
|
|
||||||
CustomShippingOption,
|
|
||||||
Discount,
|
|
||||||
DiscountRule,
|
|
||||||
DiscountRuleType,
|
|
||||||
LineItem,
|
|
||||||
PaymentSession,
|
|
||||||
PaymentSessionStatus,
|
|
||||||
SalesChannel,
|
|
||||||
ShippingMethod,
|
|
||||||
} from "../models"
|
|
||||||
import { AddressRepository } from "../repositories/address"
|
import { AddressRepository } from "../repositories/address"
|
||||||
import { CartRepository } from "../repositories/cart"
|
import { CartRepository } from "../repositories/cart"
|
||||||
import { LineItemRepository } from "../repositories/line-item"
|
import { FlagRouter } from "@medusajs/utils"
|
||||||
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 { IsNumber } from "class-validator"
|
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 = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -797,7 +798,7 @@ class CartService extends TransactionBaseService {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const items: LineItem[] = Array.isArray(lineItems) ? lineItems : [lineItems]
|
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")) {
|
if (this.featureFlagRouter_.isFeatureEnabled("sales_channels")) {
|
||||||
select.push("sales_channel_id")
|
select.push("sales_channel_id")
|
||||||
@@ -900,10 +901,33 @@ class CartService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentItem) {
|
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] = {
|
lineItemsToUpdate[currentItem.id] = {
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
has_shipping: false,
|
has_shipping: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDefined(calculated_price)) {
|
||||||
|
lineItemsToUpdate[currentItem.id].unit_price = calculated_price
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Since the variant is eager loaded, we are removing it before the line item is being created.
|
// Since the variant is eager loaded, we are removing it before the line item is being created.
|
||||||
delete (item as Partial<LineItem>).variant
|
delete (item as Partial<LineItem>).variant
|
||||||
@@ -977,8 +1001,9 @@ class CartService extends TransactionBaseService {
|
|||||||
async updateLineItem(
|
async updateLineItem(
|
||||||
cartId: string,
|
cartId: string,
|
||||||
lineItemId: string,
|
lineItemId: string,
|
||||||
lineItemUpdate: LineItemUpdate
|
update: LineItemUpdate
|
||||||
): Promise<Cart> {
|
): Promise<Cart> {
|
||||||
|
const { should_calculate_prices, ...lineItemUpdate } = update
|
||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const select: (keyof Cart)[] = ["id", "region_id", "customer_id"]
|
const select: (keyof Cart)[] = ["id", "region_id", "customer_id"]
|
||||||
@@ -1021,24 +1046,26 @@ class CartService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const variantsPricing = await this.pricingService_
|
if (should_calculate_prices) {
|
||||||
.withTransaction(transactionManager)
|
const variantsPricing = await this.pricingService_
|
||||||
.getProductVariantsPricing(
|
.withTransaction(transactionManager)
|
||||||
[
|
.getProductVariantsPricing(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
variantId: lineItem.variant_id,
|
||||||
|
quantity: lineItemUpdate.quantity,
|
||||||
|
},
|
||||||
|
],
|
||||||
{
|
{
|
||||||
variantId: lineItem.variant_id,
|
region_id: cart.region_id,
|
||||||
quantity: lineItemUpdate.quantity,
|
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]
|
const { calculated_price } = variantsPricing[lineItem.variant_id]
|
||||||
lineItemUpdate.unit_price = calculated_price ?? undefined
|
lineItemUpdate.unit_price = calculated_price ?? undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { ValidateNested } from "class-validator"
|
import {
|
||||||
import { IsType } from "../utils/validators/is-type"
|
AddressPayload,
|
||||||
|
DateComparisonOperator,
|
||||||
|
StringComparisonOperator,
|
||||||
|
} from "./common"
|
||||||
import { Cart, CartType } from "../models/cart"
|
import { Cart, CartType } from "../models/cart"
|
||||||
import { AddressPayload, DateComparisonOperator, StringComparisonOperator } from "./common"
|
|
||||||
|
import { IsType } from "../utils/validators/is-type"
|
||||||
import { Region } from "../models"
|
import { Region } from "../models"
|
||||||
|
import { ValidateNested } from "class-validator"
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function isCart(object: any): object is Cart {
|
export function isCart(object: any): object is Cart {
|
||||||
@@ -29,10 +34,11 @@ export type LineItemUpdate = {
|
|||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
region_id?: string
|
region_id?: string
|
||||||
variant_id?: string
|
variant_id?: string
|
||||||
|
should_calculate_prices?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LineItemValidateData = {
|
export type LineItemValidateData = {
|
||||||
variant?: { product_id: string };
|
variant?: { product_id: string }
|
||||||
variant_id: string
|
variant_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user