Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(pricing): PriceLists of type Sale should not override lower prices #10882

Merged
merged 3 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quick-buttons-raise.md
Original file line number Diff line number Diff line change
@@ -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.
166 changes: 166 additions & 0 deletions integration-tests/http/__tests__/product/store/product.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,89 @@ medusaIntegrationTestRunner({
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&region_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 () => {
const priceList = (
await api.post(
Expand Down Expand Up @@ -1254,6 +1337,89 @@ medusaIntegrationTestRunner({
expect(response.status).toEqual(200)
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&region_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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
await createPriceLists(service, { type: PriceListType.OVERRIDE })

Expand Down
30 changes: 26 additions & 4 deletions packages/modules/pricing/src/services/pricing-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
InjectTransactionManager,
isPresent,
isString,
MathBN,
MedusaContext,
MedusaError,
ModulesSdkUtils,
Expand All @@ -50,10 +51,10 @@ import {
PriceSet,
} from "@models"

import { Collection } from "@mikro-orm/core"
import { ServiceTypes } from "@types"
import { eventBuilders, validatePriceListDates } from "@utils"
import { joinerConfig } from "../joiner-config"
import { Collection } from "@mikro-orm/core"

type InjectedDependencies = {
baseRepository: DAL.RepositoryService
Expand Down Expand Up @@ -288,11 +289,32 @@ export default class PricingModuleService
let originalPrice: PricingTypes.CalculatedPriceSetDTO | undefined =
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) {
calculatedPrice = priceListPrice
switch (priceListPrice.price_list_type) {
case PriceListType.OVERRIDE:
calculatedPrice = priceListPrice
originalPrice = priceListPrice
break
case PriceListType.SALE: {
let lowestPrice = priceListPrice

if (defaultPrice?.amount && priceListPrice.amount) {
lowestPrice = MathBN.lte(
priceListPrice.amount,
defaultPrice.amount
)
? priceListPrice
: defaultPrice
}

if (priceListPrice.price_list_type === PriceListType.OVERRIDE) {
originalPrice = priceListPrice
calculatedPrice = lowestPrice
break
}
}
}

Expand Down
Loading