feat(core-flows,pricing,medusa,pricing,types,utils): Price List Prices can have their own rules (#5752)

**What**
- Add price-rules for prices in price-lists
- make rules object optional when creating prices

**Why**
- more price granularity

Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com>
This commit is contained in:
Riqwan Thamir
2023-12-12 08:20:21 +00:00
committed by GitHub
co-authored by Philip Korsholm
parent f05c20695e
commit 079f0da83f
41 changed files with 984 additions and 370 deletions
@@ -36,7 +36,6 @@ describe("PriceSet Service", () => {
id: "money-amount-USD",
currency_code: "EUR",
amount: 100,
rules: {},
},
],
},
@@ -18,7 +18,7 @@ const defaultRules = {
region_id: ["DE", "DK"],
}
const defaultPriceListPrices = [
const defaultPriceListPrices: PricingTypes.PriceListPriceDTO[] = [
{
amount: 232,
currency_code: "PLN",
@@ -170,63 +170,63 @@ describe("PricingModule Service - Calculate Price", () => {
title: "psma PLN",
price_set: "price-set-PLN",
money_amount: "money-amount-PLN",
number_rules: 0,
rules_count: 0,
},
{
id: "psma-company_id-EUR",
title: "psma EUR - company_id",
price_set: "price-set-EUR",
money_amount: "money-amount-company_id-EUR",
number_rules: 1,
rules_count: 1,
},
{
id: "psma-company_id-PLN",
title: "psma PLN - company_id",
price_set: "price-set-PLN",
money_amount: "money-amount-company_id-PLN",
number_rules: 1,
rules_count: 1,
},
{
id: "psma-region_id-PLN",
title: "psma PLN - region_id",
price_set: "price-set-PLN",
money_amount: "money-amount-region_id-PLN",
number_rules: 1,
rules_count: 1,
},
{
id: "psma-region_id+company_id-PLN",
title: "psma region_id + company_id",
price_set: "price-set-PLN",
money_amount: "money-amount-region_id+company_id-PLN",
number_rules: 2,
rules_count: 2,
},
{
id: "psma-region_id-PLN-5-qty",
title: "psma PLN - region_id 5 qty",
price_set: "price-set-PLN",
money_amount: "money-amount-region_id-PLN-5-qty",
number_rules: 1,
rules_count: 1,
},
{
id: "psma-region_id_company_id-PL-EUR",
title: "psma PLN - region_id PL with EUR currency",
price_set: "price-set-PLN",
money_amount: "money-amount-region_id-PL-EUR",
number_rules: 2,
rules_count: 2,
},
{
id: "psma-region_id_company_id-PL-EUR-4-qty",
title: "psma PLN - region_id PL with EUR currency for quantity 4",
price_set: "price-set-PLN",
money_amount: "money-amount-region_id-PL-EUR-4-qty",
number_rules: 2,
rules_count: 2,
},
{
id: "psma-region_id_company_id-PL-EUR-customer-group",
title: "psma PLN - region_id PL with EUR currency for customer group",
price_set: "price-set-PLN",
money_amount: "money-amount-region_id-PL-EUR-customer-group",
number_rules: 3,
rules_count: 3,
},
]
@@ -1846,6 +1846,152 @@ describe("PricingModule Service - Calculate Price", () => {
},
])
})
it("should return price list prices when price list conditions match within prices", async () => {
await createPriceLists(service, {}, { region_id: ["DE", "PL"] }, [
...defaultPriceListPrices,
{
amount: 111,
currency_code: "PLN",
price_set_id: "price-set-PLN",
rules: {
region_id: "DE",
},
},
])
const priceSetsResult = await service.calculatePrices(
{ id: ["price-set-EUR", "price-set-PLN"] },
{
context: {
currency_code: "PLN",
region_id: "DE",
customer_group_id: "vip-customer-group-id",
company_id: "medusa-company-id",
},
}
)
expect(priceSetsResult).toEqual([
{
id: "price-set-EUR",
is_calculated_price_price_list: false,
calculated_amount: null,
is_original_price_price_list: false,
original_amount: null,
currency_code: null,
calculated_price: {
money_amount_id: null,
price_list_id: null,
price_list_type: null,
min_quantity: null,
max_quantity: null,
},
original_price: {
money_amount_id: null,
price_list_id: null,
price_list_type: null,
min_quantity: null,
max_quantity: null,
},
},
{
id: "price-set-PLN",
is_calculated_price_price_list: true,
calculated_amount: 111,
is_original_price_price_list: false,
original_amount: 400,
currency_code: "PLN",
calculated_price: {
money_amount_id: expect.any(String),
price_list_id: expect.any(String),
price_list_type: "sale",
min_quantity: null,
max_quantity: null,
},
original_price: {
money_amount_id: expect.any(String),
price_list_id: null,
price_list_type: null,
min_quantity: 1,
max_quantity: 5,
},
},
])
})
it("should not return price list prices when price list conditions are met but price rules are not", async () => {
await createPriceLists(service, {}, { region_id: ["DE", "PL"] }, [
...defaultPriceListPrices,
{
amount: 111,
currency_code: "PLN",
price_set_id: "price-set-PLN",
rules: {
region_id: "PL",
},
},
])
const priceSetsResult = await service.calculatePrices(
{ id: ["price-set-EUR", "price-set-PLN"] },
{
context: {
currency_code: "PLN",
region_id: "DE",
customer_group_id: "vip-customer-group-id",
company_id: "medusa-company-id",
},
}
)
expect(priceSetsResult).toEqual([
{
id: "price-set-EUR",
is_calculated_price_price_list: false,
calculated_amount: null,
is_original_price_price_list: false,
original_amount: null,
currency_code: null,
calculated_price: {
money_amount_id: null,
price_list_id: null,
price_list_type: null,
min_quantity: null,
max_quantity: null,
},
original_price: {
money_amount_id: null,
price_list_id: null,
price_list_type: null,
min_quantity: null,
max_quantity: null,
},
},
{
id: "price-set-PLN",
is_calculated_price_price_list: true,
calculated_amount: 232,
is_original_price_price_list: false,
original_amount: 400,
currency_code: "PLN",
calculated_price: {
money_amount_id: expect.any(String),
price_list_id: expect.any(String),
price_list_type: "sale",
min_quantity: null,
max_quantity: null,
},
original_price: {
money_amount_id: expect.any(String),
price_list_id: null,
price_list_type: null,
min_quantity: 1,
max_quantity: 5,
},
},
])
})
})
})
})
@@ -28,6 +28,16 @@ describe("PriceList Service", () => {
await createCurrencies(testManager)
await createPriceSets(testManager)
await createPriceLists(testManager)
await service.createRuleTypes([
{
name: "Region ID",
rule_attribute: "region_id",
},
{
name: "Customer Group ID",
rule_attribute: "customer_group_id",
},
])
})
afterEach(async () => {
@@ -35,7 +45,7 @@ describe("PriceList Service", () => {
})
describe("list", () => {
it("list priceLists", async () => {
it("should list priceLists", async () => {
const priceListResult = await service.listPriceLists()
expect(priceListResult).toEqual([
@@ -48,7 +58,7 @@ describe("PriceList Service", () => {
])
})
it("list pricelists by id", async () => {
it("should list pricelists by id", async () => {
const priceListResult = await service.listPriceLists({
id: ["price-list-1"],
})
@@ -279,8 +289,7 @@ describe("PriceList Service", () => {
await service.updatePriceLists([
{
id: "does-not-exist",
number_rules: 2,
rules: {},
rules_count: 2,
},
])
} catch (e) {
@@ -293,7 +302,7 @@ describe("PriceList Service", () => {
})
})
describe("create", () => {
describe("createPriceLists", () => {
it("should create a priceList successfully", async () => {
const [created] = await service.createPriceLists([
{
@@ -393,5 +402,346 @@ describe("PriceList Service", () => {
})
)
})
it("should create a price list with granular rules within prices", async () => {
const [created] = await service.createPriceLists([
{
title: "test",
description: "test",
starts_at: "10/01/2023",
ends_at: "10/30/2023",
rules: {
customer_group_id: [
"vip-customer-group-id",
"another-vip-customer-group-id",
],
region_id: ["DE", "DK"],
},
prices: [
{
amount: 400,
currency_code: "EUR",
price_set_id: "price-set-1",
rules: {
region_id: "DE",
},
},
{
amount: 600,
currency_code: "EUR",
price_set_id: "price-set-1",
},
],
},
])
const [priceList] = await service.listPriceLists(
{
id: [created.id],
},
{
relations: [
"price_set_money_amounts.money_amount",
"price_set_money_amounts.price_set",
"price_set_money_amounts.price_rules",
"price_list_rules.price_list_rule_values",
"price_list_rules.rule_type",
],
select: [
"id",
"price_set_money_amounts.price_rules.value",
"price_set_money_amounts.rules_count",
"price_set_money_amounts.money_amount.amount",
"price_set_money_amounts.money_amount.currency_code",
"price_set_money_amounts.money_amount.price_list_id",
"price_list_rules.price_list_rule_values.value",
"price_list_rules.rule_type.rule_attribute",
],
}
)
expect(priceList).toEqual(
expect.objectContaining({
id: expect.any(String),
price_set_money_amounts: expect.arrayContaining([
expect.objectContaining({
rules_count: 1,
price_rules: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: "DE",
}),
]),
price_list: expect.objectContaining({
id: expect.any(String),
}),
money_amount: expect.objectContaining({
amount: 400,
currency_code: "EUR",
}),
}),
expect.objectContaining({
rules_count: 0,
price_rules: [],
price_list: expect.objectContaining({
id: expect.any(String),
}),
money_amount: expect.objectContaining({
amount: 600,
currency_code: "EUR",
}),
}),
]),
price_list_rules: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
rule_type: expect.objectContaining({
id: expect.any(String),
rule_attribute: "customer_group_id",
}),
price_list_rule_values: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: "vip-customer-group-id",
}),
expect.objectContaining({
id: expect.any(String),
value: "another-vip-customer-group-id",
}),
]),
}),
expect.objectContaining({
id: expect.any(String),
rule_type: expect.objectContaining({
id: expect.any(String),
rule_attribute: "region_id",
}),
price_list_rule_values: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: "DE",
}),
expect.objectContaining({
id: expect.any(String),
value: "DK",
}),
]),
}),
]),
})
)
})
it("should throw error when rule type does not exist", async () => {
const error = await service
.createPriceLists([
{
title: "test",
description: "test",
rules: {
region_id: ["DE", "DK"],
missing_1: ["test-missing-1"],
},
prices: [
{
amount: 400,
currency_code: "EUR",
price_set_id: "price-set-1",
rules: {
region_id: "DE",
missing_2: "test-missing-2",
},
},
],
},
])
.catch((e) => e)
expect(error.message).toEqual(
"Cannot find RuleTypes with rule_attribute - missing_1, missing_2"
)
})
})
describe("addPriceListPrices", () => {
it("should add a price to a priceList successfully", async () => {
await service.addPriceListPrices([
{
priceListId: "price-list-1",
prices: [
{
amount: 123,
currency_code: "EUR",
price_set_id: "price-set-1",
},
],
},
])
const [priceList] = await service.listPriceLists(
{
id: ["price-list-1"],
},
{
relations: [
"price_set_money_amounts.money_amount",
"price_set_money_amounts.price_set",
"price_set_money_amounts.price_rules",
"price_list_rules.price_list_rule_values",
"price_list_rules.rule_type",
],
select: [
"id",
"price_set_money_amounts.price_rules.value",
"price_set_money_amounts.rules_count",
"price_set_money_amounts.money_amount.amount",
"price_set_money_amounts.money_amount.currency_code",
"price_set_money_amounts.money_amount.price_list_id",
"price_list_rules.price_list_rule_values.value",
"price_list_rules.rule_type.rule_attribute",
],
}
)
expect(priceList).toEqual(
expect.objectContaining({
id: expect.any(String),
price_set_money_amounts: expect.arrayContaining([
expect.objectContaining({
rules_count: 0,
price_list: expect.objectContaining({
id: expect.any(String),
}),
money_amount: expect.objectContaining({
amount: 123,
currency_code: "EUR",
}),
}),
]),
price_list_rules: [],
})
)
})
it("should fail to add a price with non-existing rule-types in the price-set to a priceList", async () => {
await service.createRuleTypes([
{
name: "twitter_handle",
rule_attribute: "twitter_handle",
},
])
let error
try {
await service.addPriceListPrices([
{
priceListId: "price-list-1",
prices: [
{
amount: 123,
currency_code: "EUR",
price_set_id: "price-set-1",
rules: {
twitter_handle: "owjuhl",
},
},
],
},
])
} catch (err) {
error = err
}
expect(error.message).toEqual(
"" +
`Invalid rule type configuration: Price set rules doesn't exist for rule_attribute "twitter_handle" in price set price-set-1`
)
})
it("should add a price with rules to a priceList successfully", async () => {
await service.createRuleTypes([
{
name: "region_id",
rule_attribute: "region_id",
},
])
const r = await service.addRules([
{
priceSetId: "price-set-1",
rules: [{ attribute: "region_id" }],
},
])
await service.addPriceListPrices([
{
priceListId: "price-list-1",
prices: [
{
amount: 123,
currency_code: "EUR",
price_set_id: "price-set-1",
rules: {
region_id: "EU",
},
},
],
},
])
const [priceList] = await service.listPriceLists(
{
id: ["price-list-1"],
},
{
relations: [
"price_set_money_amounts.money_amount",
"price_set_money_amounts.price_set",
"price_set_money_amounts.price_rules",
"price_set_money_amounts.price_rules.rule_type",
"price_list_rules.price_list_rule_values",
"price_list_rules.rule_type",
],
select: [
"id",
"price_set_money_amounts.price_rules.value",
"price_set_money_amounts.price_rules.rule_type.rule_attribute",
"price_set_money_amounts.rules_count",
"price_set_money_amounts.money_amount.amount",
"price_set_money_amounts.money_amount.currency_code",
"price_set_money_amounts.money_amount.price_list_id",
"price_list_rules.price_list_rule_values.value",
"price_list_rules.rule_type.rule_attribute",
],
}
)
expect(priceList).toEqual(
expect.objectContaining({
id: expect.any(String),
price_set_money_amounts: expect.arrayContaining([
expect.objectContaining({
rules_count: 1,
price_list: expect.objectContaining({
id: expect.any(String),
}),
price_rules: [
expect.objectContaining({
value: "EU",
rule_type: expect.objectContaining({
rule_attribute: "region_id",
}),
}),
],
money_amount: expect.objectContaining({
amount: 123,
currency_code: "EUR",
}),
}),
]),
price_list_rules: [],
})
)
})
})
})
@@ -6,7 +6,7 @@ import {
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { PriceSet } from "@models"
import { PriceSetRuleType, initialize } from "../../../../src"
import { initialize, PriceSetRuleType } from "../../../../src"
import { seedPriceData } from "../../../__fixtures__/seed-price-data"
import { DB_URL, MikroOrmWrapper } from "../../../utils"
@@ -400,7 +400,6 @@ describe("PricingModule Service - PriceSet", () => {
{
amount: 150,
currency_code: "USD",
rules: {},
},
],
},