fix(pricing): PriceLists of type Sale should not override lower prices (#10882)
Resolves CMRC-840
This commit is contained in:
1 parent
3ee15f3b85
commit
a625bce7b0
4 files changed
+320
-4
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/pricing": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(pricing): PriceLists of type Sale no longer override default prices when the price list price is higher than the default price.
|
||||||
@@ -1172,6 +1172,89 @@ medusaIntegrationTestRunner({
|
|||||||
expect(response.data.products).toEqual(expectation)
|
expect(response.data.products).toEqual(expectation)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should list products with prices with a default price when the price list price is higher and the price list is of type SALE", async () => {
|
||||||
|
const priceList = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/price-lists`,
|
||||||
|
{
|
||||||
|
title: "test price list",
|
||||||
|
description: "test",
|
||||||
|
status: PriceListStatus.ACTIVE,
|
||||||
|
type: PriceListType.SALE,
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: 3500,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: { "customer.groups.id": [customerGroup.id] },
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.price_list
|
||||||
|
|
||||||
|
let response = await api.get(
|
||||||
|
`/store/products?fields=*variants.calculated_price®ion_id=${region.id}`,
|
||||||
|
storeHeadersWithCustomer
|
||||||
|
)
|
||||||
|
|
||||||
|
const expectation = expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: product.id,
|
||||||
|
variants: [
|
||||||
|
expect.objectContaining({
|
||||||
|
calculated_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
is_calculated_price_price_list: false,
|
||||||
|
is_calculated_price_tax_inclusive: false,
|
||||||
|
calculated_amount: 3000,
|
||||||
|
raw_calculated_amount: {
|
||||||
|
value: "3000",
|
||||||
|
precision: 20,
|
||||||
|
},
|
||||||
|
is_original_price_price_list: false,
|
||||||
|
is_original_price_tax_inclusive: false,
|
||||||
|
original_amount: 3000,
|
||||||
|
raw_original_amount: {
|
||||||
|
value: "3000",
|
||||||
|
precision: 20,
|
||||||
|
},
|
||||||
|
currency_code: "usd",
|
||||||
|
calculated_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: null,
|
||||||
|
price_list_type: null,
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
},
|
||||||
|
original_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: null,
|
||||||
|
price_list_type: null,
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.count).toEqual(3)
|
||||||
|
expect(response.data.products).toEqual(expectation)
|
||||||
|
|
||||||
|
// with only region_id
|
||||||
|
response = await api.get(
|
||||||
|
`/store/products?region_id=${region.id}`,
|
||||||
|
storeHeadersWithCustomer
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.products).toEqual(expectation)
|
||||||
|
})
|
||||||
|
|
||||||
it("should list products with prices with a override price list price", async () => {
|
it("should list products with prices with a override price list price", async () => {
|
||||||
const priceList = (
|
const priceList = (
|
||||||
await api.post(
|
await api.post(
|
||||||
@@ -1254,6 +1337,89 @@ medusaIntegrationTestRunner({
|
|||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.products).toEqual(expectation)
|
expect(response.data.products).toEqual(expectation)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should list products with prices with a override price list price even if the price list price is higher than the default price", async () => {
|
||||||
|
const priceList = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/price-lists`,
|
||||||
|
{
|
||||||
|
title: "test price list",
|
||||||
|
description: "test",
|
||||||
|
status: PriceListStatus.ACTIVE,
|
||||||
|
type: PriceListType.OVERRIDE,
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: 35000,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: { "customer.groups.id": [customerGroup.id] },
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.price_list
|
||||||
|
|
||||||
|
let response = await api.get(
|
||||||
|
`/store/products?fields=*variants.calculated_price®ion_id=${region.id}`,
|
||||||
|
storeHeadersWithCustomer
|
||||||
|
)
|
||||||
|
|
||||||
|
const expectation = expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: product.id,
|
||||||
|
variants: [
|
||||||
|
expect.objectContaining({
|
||||||
|
calculated_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
is_calculated_price_price_list: true,
|
||||||
|
is_calculated_price_tax_inclusive: false,
|
||||||
|
calculated_amount: 35000,
|
||||||
|
raw_calculated_amount: {
|
||||||
|
value: "35000",
|
||||||
|
precision: 20,
|
||||||
|
},
|
||||||
|
is_original_price_price_list: true,
|
||||||
|
is_original_price_tax_inclusive: false,
|
||||||
|
original_amount: 35000,
|
||||||
|
raw_original_amount: {
|
||||||
|
value: "35000",
|
||||||
|
precision: 20,
|
||||||
|
},
|
||||||
|
currency_code: "usd",
|
||||||
|
calculated_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: priceList.id,
|
||||||
|
price_list_type: "override",
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
},
|
||||||
|
original_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: priceList.id,
|
||||||
|
price_list_type: "override",
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.count).toEqual(3)
|
||||||
|
expect(response.data.products).toEqual(expectation)
|
||||||
|
|
||||||
|
// with only region_id
|
||||||
|
response = await api.get(
|
||||||
|
`/store/products?region_id=${region.id}`,
|
||||||
|
storeHeadersWithCustomer
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.products).toEqual(expectation)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("with inventory items", () => {
|
describe("with inventory items", () => {
|
||||||
|
|||||||
+123
@@ -1167,6 +1167,129 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should return default prices when the price list price is higher than the default price when the price list is of type SALE", async () => {
|
||||||
|
await createPriceLists(service, undefined, undefined, [
|
||||||
|
{
|
||||||
|
amount: 2500,
|
||||||
|
currency_code: "PLN",
|
||||||
|
price_set_id: "price-set-PLN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 2500,
|
||||||
|
currency_code: "EUR",
|
||||||
|
price_set_id: "price-set-EUR",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const priceSetsResult = await service.calculatePrices(
|
||||||
|
{ id: ["price-set-EUR", "price-set-PLN"] },
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
currency_code: "PLN",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(priceSetsResult).toEqual([
|
||||||
|
{
|
||||||
|
id: "price-set-PLN",
|
||||||
|
is_calculated_price_price_list: false,
|
||||||
|
is_calculated_price_tax_inclusive: false,
|
||||||
|
calculated_amount: 1000,
|
||||||
|
raw_calculated_amount: {
|
||||||
|
value: "1000",
|
||||||
|
precision: 20,
|
||||||
|
},
|
||||||
|
is_original_price_price_list: false,
|
||||||
|
is_original_price_tax_inclusive: false,
|
||||||
|
original_amount: 1000,
|
||||||
|
raw_original_amount: {
|
||||||
|
value: "1000",
|
||||||
|
precision: 20,
|
||||||
|
},
|
||||||
|
currency_code: "PLN",
|
||||||
|
calculated_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: null,
|
||||||
|
price_list_type: null,
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 10,
|
||||||
|
},
|
||||||
|
original_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: null,
|
||||||
|
price_list_type: null,
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return price list prices even if the price list price is higher than the default price when the price list is of type OVERRIDE", async () => {
|
||||||
|
await createPriceLists(
|
||||||
|
service,
|
||||||
|
{ type: PriceListType.OVERRIDE },
|
||||||
|
{},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
amount: 2500,
|
||||||
|
currency_code: "PLN",
|
||||||
|
price_set_id: "price-set-PLN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 2500,
|
||||||
|
currency_code: "EUR",
|
||||||
|
price_set_id: "price-set-EUR",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
const priceSetsResult = await service.calculatePrices(
|
||||||
|
{ id: ["price-set-EUR", "price-set-PLN"] },
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
currency_code: "PLN",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(priceSetsResult).toEqual([
|
||||||
|
{
|
||||||
|
id: "price-set-PLN",
|
||||||
|
is_calculated_price_price_list: true,
|
||||||
|
is_calculated_price_tax_inclusive: false,
|
||||||
|
calculated_amount: 2500,
|
||||||
|
raw_calculated_amount: {
|
||||||
|
value: "2500",
|
||||||
|
precision: 20,
|
||||||
|
},
|
||||||
|
is_original_price_price_list: true,
|
||||||
|
is_original_price_tax_inclusive: false,
|
||||||
|
original_amount: 2500,
|
||||||
|
raw_original_amount: {
|
||||||
|
value: "2500",
|
||||||
|
precision: 20,
|
||||||
|
},
|
||||||
|
currency_code: "PLN",
|
||||||
|
calculated_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: expect.any(String),
|
||||||
|
price_list_type: "override",
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
},
|
||||||
|
original_price: {
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: expect.any(String),
|
||||||
|
price_list_type: "override",
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it("should return price list prices when price list conditions match for override", async () => {
|
it("should return price list prices when price list conditions match for override", async () => {
|
||||||
await createPriceLists(service, { type: PriceListType.OVERRIDE })
|
await createPriceLists(service, { type: PriceListType.OVERRIDE })
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
isPresent,
|
isPresent,
|
||||||
isString,
|
isString,
|
||||||
|
MathBN,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
@@ -50,10 +51,10 @@ import {
|
|||||||
PriceSet,
|
PriceSet,
|
||||||
} from "@models"
|
} from "@models"
|
||||||
|
|
||||||
|
import { Collection } from "@mikro-orm/core"
|
||||||
import { ServiceTypes } from "@types"
|
import { ServiceTypes } from "@types"
|
||||||
import { eventBuilders, validatePriceListDates } from "@utils"
|
import { eventBuilders, validatePriceListDates } from "@utils"
|
||||||
import { joinerConfig } from "../joiner-config"
|
import { joinerConfig } from "../joiner-config"
|
||||||
import { Collection } from "@mikro-orm/core"
|
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
baseRepository: DAL.RepositoryService
|
baseRepository: DAL.RepositoryService
|
||||||
@@ -288,11 +289,32 @@ export default class PricingModuleService
|
|||||||
let originalPrice: PricingTypes.CalculatedPriceSetDTO | undefined =
|
let originalPrice: PricingTypes.CalculatedPriceSetDTO | undefined =
|
||||||
defaultPrice
|
defaultPrice
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When deciding which price to use we follow the following logic:
|
||||||
|
* - If the price list is of type OVERRIDE, we always use the price list price.
|
||||||
|
* - If the price list is of type SALE, we use the lowest price between the price list price and the default price
|
||||||
|
*/
|
||||||
if (priceListPrice) {
|
if (priceListPrice) {
|
||||||
calculatedPrice = priceListPrice
|
switch (priceListPrice.price_list_type) {
|
||||||
|
case PriceListType.OVERRIDE:
|
||||||
|
calculatedPrice = priceListPrice
|
||||||
|
originalPrice = priceListPrice
|
||||||
|
break
|
||||||
|
case PriceListType.SALE: {
|
||||||
|
let lowestPrice = priceListPrice
|
||||||
|
|
||||||
if (priceListPrice.price_list_type === PriceListType.OVERRIDE) {
|
if (defaultPrice?.amount && priceListPrice.amount) {
|
||||||
originalPrice = priceListPrice
|
lowestPrice = MathBN.lte(
|
||||||
|
priceListPrice.amount,
|
||||||
|
defaultPrice.amount
|
||||||
|
)
|
||||||
|
? priceListPrice
|
||||||
|
: defaultPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedPrice = lowestPrice
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user