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:
9
.changeset/chilly-mayflies-melt.md
Normal file
9
.changeset/chilly-mayflies-melt.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"@medusajs/core-flows": patch
|
||||
"@medusajs/pricing": patch
|
||||
"@medusajs/medusa": patch
|
||||
"@medusajs/types": patch
|
||||
"@medusajs/utils": patch
|
||||
---
|
||||
|
||||
feat(core-flows,pricing,medusa,pricing,types,utils): Price List Prices can have their own rules
|
||||
@@ -90,8 +90,8 @@ describe("POST /admin/price-lists/:id/prices/batch", () => {
|
||||
{
|
||||
title: "test price list",
|
||||
description: "test",
|
||||
ends_at: new Date(),
|
||||
starts_at: new Date(),
|
||||
ends_at: new Date().toISOString(),
|
||||
starts_at: new Date().toISOString(),
|
||||
status: PriceListStatus.ACTIVE,
|
||||
type: PriceListType.OVERRIDE,
|
||||
},
|
||||
@@ -104,7 +104,6 @@ describe("POST /admin/price-lists/:id/prices/batch", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -117,6 +116,11 @@ describe("POST /admin/price-lists/:id/prices/batch", () => {
|
||||
amount: 5000,
|
||||
currency_code: "usd",
|
||||
},
|
||||
{
|
||||
amount: 6000,
|
||||
region_id: "test-region",
|
||||
variant_id: variant.id,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -184,6 +188,44 @@ describe("POST /admin/price-lists/:id/prices/batch", () => {
|
||||
}),
|
||||
variant_id: expect.any(String),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
deleted_at: null,
|
||||
currency_code: "usd",
|
||||
amount: 6000,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
price_list_id: expect.any(String),
|
||||
region_id: "test-region",
|
||||
variant: expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
deleted_at: null,
|
||||
title: expect.any(String),
|
||||
product_id: expect.any(String),
|
||||
sku: null,
|
||||
barcode: null,
|
||||
ean: null,
|
||||
upc: null,
|
||||
variant_rank: 0,
|
||||
inventory_quantity: 10,
|
||||
allow_backorder: false,
|
||||
manage_inventory: true,
|
||||
hs_code: null,
|
||||
origin_country: null,
|
||||
mid_code: null,
|
||||
material: null,
|
||||
weight: null,
|
||||
length: null,
|
||||
height: null,
|
||||
width: null,
|
||||
metadata: null,
|
||||
}),
|
||||
variant_id: expect.any(String),
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
@@ -94,7 +94,6 @@ describe("POST /admin/price-lists", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -87,7 +87,6 @@ describe("DELETE /admin/price-lists/:id/products/:productId/batch", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -89,7 +89,6 @@ describe("DELETE /admin/price-lists/:id/variants/:variantId/prices", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -89,7 +89,6 @@ describe("DELETE /admin/price-lists/:id", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -89,7 +89,6 @@ describe("DELETE /admin/price-lists/:id", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -81,7 +81,6 @@ describe("GET /admin/price-lists/:id", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
rules: [],
|
||||
|
||||
@@ -99,7 +99,6 @@ describe("GET /admin/price-lists/:id/products", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
rules: [],
|
||||
@@ -210,7 +209,6 @@ describe("GET /admin/price-lists/:id/products", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
rules: [],
|
||||
|
||||
@@ -81,7 +81,6 @@ describe("GET /admin/price-lists", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
rules: [],
|
||||
|
||||
@@ -131,7 +131,6 @@ describe("POST /admin/price-lists/:id", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -91,12 +91,10 @@ describe("GET /store/products/:id", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
amount: 4000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
rules: [],
|
||||
|
||||
@@ -140,7 +140,6 @@ describe("POST /admin/products/:id/variants/:id", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -142,7 +142,6 @@ describe("POST /admin/products/:id", () => {
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -9,7 +9,6 @@ const defaultPrices = [
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
rules: {},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export async function prepareCreatePriceLists({
|
||||
})[]
|
||||
}>): Promise<Result | void> {
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
const regionService = container.resolve("regionService")
|
||||
|
||||
const { price_lists } = data
|
||||
|
||||
@@ -59,6 +60,17 @@ export async function prepareCreatePriceLists({
|
||||
)
|
||||
}
|
||||
|
||||
const regionIds = price_lists
|
||||
.map(({ prices }) => prices?.map((price) => price.region_id) ?? [])
|
||||
.flat(2)
|
||||
const regions = await regionService.list({ id: regionIds }, {})
|
||||
const regionIdCurrencyCodeMap: Map<string, string> = new Map(
|
||||
regions.map((region: { id: string; currency_code: string }) => [
|
||||
region.id,
|
||||
region.currency_code,
|
||||
])
|
||||
)
|
||||
|
||||
return price_lists.map((priceListDTO) => {
|
||||
priceListDTO.title ??= priceListDTO.name
|
||||
const { _associationTag, name, prices, ...rest } = priceListDTO
|
||||
@@ -70,12 +82,20 @@ export async function prepareCreatePriceLists({
|
||||
prices?.map((price) => {
|
||||
const price_set_id = variantIdPriceSetIdMap.get(price.variant_id)!
|
||||
|
||||
const rules: Record<string, string> = {}
|
||||
if (price.region_id) {
|
||||
rules.region_id = price.region_id
|
||||
}
|
||||
|
||||
return {
|
||||
currency_code: price.currency_code,
|
||||
currency_code:
|
||||
regionIdCurrencyCodeMap.get(price.region_id as string) ??
|
||||
(price.currency_code as string),
|
||||
amount: price.amount,
|
||||
min_quantity: price.min_quantity,
|
||||
max_quantity: price.max_quantity,
|
||||
price_set_id,
|
||||
rules,
|
||||
}
|
||||
}) ?? []
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export async function prepareUpdatePriceLists({
|
||||
}>): Promise<Result> {
|
||||
const { price_lists: priceListsData } = data
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
const regionService = container.resolve("regionService")
|
||||
|
||||
const variantPriceSetMap = new Map<string, string>()
|
||||
const priceListPricesMap = new Map<string, PriceListPriceDTO[]>()
|
||||
@@ -44,6 +45,17 @@ export async function prepareUpdatePriceLists({
|
||||
variantPriceSetMap.set(variant_id, price_set_id)
|
||||
}
|
||||
|
||||
const regionIds = priceListsData
|
||||
.map(({ prices }) => prices?.map((price) => price.region_id) ?? [])
|
||||
.flat(2)
|
||||
const regions = await regionService.list({ id: regionIds })
|
||||
const regionsMap: Map<string, string> = new Map(
|
||||
regions.map((region: { id: string; currency_code: string }) => [
|
||||
region.id,
|
||||
region.currency_code,
|
||||
])
|
||||
)
|
||||
|
||||
const priceLists = priceListsData.map((priceListData) => {
|
||||
const priceListPrices: PriceListPriceDTO[] = []
|
||||
|
||||
@@ -53,13 +65,21 @@ export async function prepareUpdatePriceLists({
|
||||
return
|
||||
}
|
||||
|
||||
const rules: Record<string, string> = {}
|
||||
if (price.region_id) {
|
||||
rules.region_id = price.region_id
|
||||
}
|
||||
|
||||
priceListPrices.push({
|
||||
id: priceData.id,
|
||||
price_set_id: variantPriceSetMap.get(variant_id) as string,
|
||||
currency_code: priceData.currency_code as string,
|
||||
currency_code:
|
||||
regionsMap.get(priceData.region_id as string) ??
|
||||
(priceData.currency_code as string),
|
||||
amount: priceData.amount,
|
||||
min_quantity: priceData.min_quantity,
|
||||
max_quantity: priceData.max_quantity,
|
||||
rules,
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
@@ -65,7 +65,6 @@ export async function updateProductsVariantsPrices({
|
||||
const obj = {
|
||||
amount: price.amount,
|
||||
currency_code: price.currency_code,
|
||||
rules: {},
|
||||
}
|
||||
|
||||
if (price.region_id) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MedusaV2Flag } from "@medusajs/utils"
|
||||
import { updatePriceLists } from "@medusajs/core-flows"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { MedusaV2Flag } from "@medusajs/utils"
|
||||
import { Type } from "class-transformer"
|
||||
import { IsArray, IsBoolean, IsOptional, ValidateNested } from "class-validator"
|
||||
import { EntityManager } from "typeorm"
|
||||
@@ -8,7 +9,6 @@ import { PriceList } from "../../../.."
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { getPriceListPricingModule } from "./modules-queries"
|
||||
|
||||
/**
|
||||
|
||||
@@ -121,6 +121,12 @@ export const defaultAdminPriceListRemoteQueryObject = {
|
||||
"updated_at",
|
||||
],
|
||||
},
|
||||
price_rules: {
|
||||
fields: ["value"],
|
||||
rule_type: {
|
||||
fields: ["rule_attribute"],
|
||||
},
|
||||
},
|
||||
price_set: {
|
||||
variant_link: {
|
||||
variant: {
|
||||
|
||||
@@ -65,12 +65,18 @@ export async function listAndCountPriceListPricingModule({
|
||||
priceList.prices = priceSetMoneyAmounts.map((priceSetMoneyAmount) => {
|
||||
const productVariant = priceSetMoneyAmount.price_set.variant_link.variant
|
||||
|
||||
const rules = priceSetMoneyAmount.price_rules.reduce((acc, curr) => {
|
||||
acc[curr.rule_type.rule_attribute] = curr.value
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return {
|
||||
...(priceSetMoneyAmount.money_amount as MoneyAmount),
|
||||
price_list_id: priceList.id,
|
||||
variant_id: productVariant?.id ?? null,
|
||||
variant: productVariant ?? null,
|
||||
region_id: null,
|
||||
region_id: rules["region_id"] ?? null,
|
||||
rules,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ export const defaultPriceListData = [
|
||||
id: "price-list-1",
|
||||
title: "Price List 1",
|
||||
description: "test",
|
||||
number_rules: 0,
|
||||
rules_count: 0,
|
||||
},
|
||||
{
|
||||
id: "price-list-2",
|
||||
title: "Price List 2",
|
||||
description: "test",
|
||||
number_rules: 0,
|
||||
rules_count: 0,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -4,20 +4,20 @@ export const defaultPriceSetMoneyAmountsData = [
|
||||
title: "price set money amount USD",
|
||||
price_set: "price-set-1",
|
||||
money_amount: "money-amount-USD",
|
||||
number_rules: 1,
|
||||
rules_count: 1,
|
||||
},
|
||||
{
|
||||
id: "price-set-money-amount-EUR",
|
||||
title: "price set money amount EUR",
|
||||
price_set: "price-set-2",
|
||||
money_amount: "money-amount-EUR",
|
||||
number_rules: 1,
|
||||
rules_count: 1,
|
||||
},
|
||||
{
|
||||
id: "price-set-money-amount-CAD",
|
||||
title: "price set money amount CAD",
|
||||
price_set: "price-set-3",
|
||||
money_amount: "money-amount-CAD",
|
||||
number_rules: 1,
|
||||
rules_count: 1,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"namespaces": [
|
||||
"public"
|
||||
],
|
||||
"namespaces": ["public"],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
@@ -48,9 +46,7 @@
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "currency_pkey",
|
||||
"columnNames": [
|
||||
"code"
|
||||
],
|
||||
"columnNames": ["code"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -143,18 +139,14 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"currency_code"
|
||||
],
|
||||
"columnNames": ["currency_code"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_money_amount_currency_code",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_money_amount_deleted_at",
|
||||
"primary": false,
|
||||
@@ -162,9 +154,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "money_amount_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -174,13 +164,9 @@
|
||||
"foreignKeys": {
|
||||
"money_amount_currency_code_foreign": {
|
||||
"constraintName": "money_amount_currency_code_foreign",
|
||||
"columnNames": [
|
||||
"currency_code"
|
||||
],
|
||||
"columnNames": ["currency_code"],
|
||||
"localTableName": "public.money_amount",
|
||||
"referencedColumnNames": [
|
||||
"code"
|
||||
],
|
||||
"referencedColumnNames": ["code"],
|
||||
"referencedTableName": "public.currency",
|
||||
"deleteRule": "set null",
|
||||
"updateRule": "cascade"
|
||||
@@ -224,10 +210,7 @@
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "'draft'",
|
||||
"enumItems": [
|
||||
"active",
|
||||
"draft"
|
||||
],
|
||||
"enumItems": ["active", "draft"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"type": {
|
||||
@@ -238,10 +221,7 @@
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "'sale'",
|
||||
"enumItems": [
|
||||
"sale",
|
||||
"override"
|
||||
],
|
||||
"enumItems": ["sale", "override"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"starts_at": {
|
||||
@@ -264,8 +244,8 @@
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"number_rules": {
|
||||
"name": "number_rules",
|
||||
"rules_count": {
|
||||
"name": "rules_count",
|
||||
"type": "integer",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
@@ -311,9 +291,7 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_list_deleted_at",
|
||||
"primary": false,
|
||||
@@ -321,9 +299,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "price_list_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -349,9 +325,7 @@
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "price_set_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -398,8 +372,8 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"number_rules": {
|
||||
"name": "number_rules",
|
||||
"rules_count": {
|
||||
"name": "rules_count",
|
||||
"type": "integer",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
@@ -422,36 +396,28 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"price_set_id"
|
||||
],
|
||||
"columnNames": ["price_set_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_set_money_amount_price_set_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"money_amount_id"
|
||||
],
|
||||
"columnNames": ["money_amount_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_set_money_amount_money_amount_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"money_amount_id"
|
||||
],
|
||||
"columnNames": ["money_amount_id"],
|
||||
"composite": false,
|
||||
"keyName": "price_set_money_amount_money_amount_id_unique",
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"price_list_id"
|
||||
],
|
||||
"columnNames": ["price_list_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_rule_price_list_id",
|
||||
"primary": false,
|
||||
@@ -459,9 +425,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "price_set_money_amount_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -471,39 +435,27 @@
|
||||
"foreignKeys": {
|
||||
"price_set_money_amount_price_set_id_foreign": {
|
||||
"constraintName": "price_set_money_amount_price_set_id_foreign",
|
||||
"columnNames": [
|
||||
"price_set_id"
|
||||
],
|
||||
"columnNames": ["price_set_id"],
|
||||
"localTableName": "public.price_set_money_amount",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.price_set",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"price_set_money_amount_money_amount_id_foreign": {
|
||||
"constraintName": "price_set_money_amount_money_amount_id_foreign",
|
||||
"columnNames": [
|
||||
"money_amount_id"
|
||||
],
|
||||
"columnNames": ["money_amount_id"],
|
||||
"localTableName": "public.price_set_money_amount",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.money_amount",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"price_set_money_amount_price_list_id_foreign": {
|
||||
"constraintName": "price_set_money_amount_price_list_id_foreign",
|
||||
"columnNames": [
|
||||
"price_list_id"
|
||||
],
|
||||
"columnNames": ["price_list_id"],
|
||||
"localTableName": "public.price_set_money_amount",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.price_list",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
@@ -554,9 +506,7 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"rule_attribute"
|
||||
],
|
||||
"columnNames": ["rule_attribute"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_rule_type_rule_attribute",
|
||||
"primary": false,
|
||||
@@ -564,9 +514,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "rule_type_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -609,18 +557,14 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"price_set_id"
|
||||
],
|
||||
"columnNames": ["price_set_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_set_rule_type_price_set_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["rule_type_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_set_rule_type_rule_type_id",
|
||||
"primary": false,
|
||||
@@ -628,9 +572,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "price_set_rule_type_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -640,26 +582,18 @@
|
||||
"foreignKeys": {
|
||||
"price_set_rule_type_price_set_id_foreign": {
|
||||
"constraintName": "price_set_rule_type_price_set_id_foreign",
|
||||
"columnNames": [
|
||||
"price_set_id"
|
||||
],
|
||||
"columnNames": ["price_set_id"],
|
||||
"localTableName": "public.price_set_rule_type",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.price_set",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"price_set_rule_type_rule_type_id_foreign": {
|
||||
"constraintName": "price_set_rule_type_rule_type_id_foreign",
|
||||
"columnNames": [
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["rule_type_id"],
|
||||
"localTableName": "public.price_set_rule_type",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.rule_type",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
@@ -708,18 +642,14 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"price_set_money_amount_id"
|
||||
],
|
||||
"columnNames": ["price_set_money_amount_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_set_money_amount_rules_price_set_money_amount_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["rule_type_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_set_money_amount_rules_rule_type_id",
|
||||
"primary": false,
|
||||
@@ -727,9 +657,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "price_set_money_amount_rules_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -739,26 +667,18 @@
|
||||
"foreignKeys": {
|
||||
"price_set_money_amount_rules_price_set_money_amount_id_foreign": {
|
||||
"constraintName": "price_set_money_amount_rules_price_set_money_amount_id_foreign",
|
||||
"columnNames": [
|
||||
"price_set_money_amount_id"
|
||||
],
|
||||
"columnNames": ["price_set_money_amount_id"],
|
||||
"localTableName": "public.price_set_money_amount_rules",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.price_set_money_amount",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"price_set_money_amount_rules_rule_type_id_foreign": {
|
||||
"constraintName": "price_set_money_amount_rules_rule_type_id_foreign",
|
||||
"columnNames": [
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["rule_type_id"],
|
||||
"localTableName": "public.price_set_money_amount_rules",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.rule_type",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
@@ -836,27 +756,21 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"price_set_id"
|
||||
],
|
||||
"columnNames": ["price_set_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_rule_price_set_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["rule_type_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_rule_rule_type_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"price_set_money_amount_id"
|
||||
],
|
||||
"columnNames": ["price_set_money_amount_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_rule_price_set_money_amount_id",
|
||||
"primary": false,
|
||||
@@ -864,9 +778,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "price_rule_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -876,38 +788,26 @@
|
||||
"foreignKeys": {
|
||||
"price_rule_price_set_id_foreign": {
|
||||
"constraintName": "price_rule_price_set_id_foreign",
|
||||
"columnNames": [
|
||||
"price_set_id"
|
||||
],
|
||||
"columnNames": ["price_set_id"],
|
||||
"localTableName": "public.price_rule",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.price_set",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"price_rule_rule_type_id_foreign": {
|
||||
"constraintName": "price_rule_rule_type_id_foreign",
|
||||
"columnNames": [
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["rule_type_id"],
|
||||
"localTableName": "public.price_rule",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.rule_type",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"price_rule_price_set_money_amount_id_foreign": {
|
||||
"constraintName": "price_rule_price_set_money_amount_id_foreign",
|
||||
"columnNames": [
|
||||
"price_set_money_amount_id"
|
||||
],
|
||||
"columnNames": ["price_set_money_amount_id"],
|
||||
"localTableName": "public.price_rule",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.price_set_money_amount",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
@@ -948,18 +848,14 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["rule_type_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_list_rule_rule_type_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"price_list_id"
|
||||
],
|
||||
"columnNames": ["price_list_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_list_rule_price_list_id",
|
||||
"primary": false,
|
||||
@@ -967,19 +863,14 @@
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_price_list_rule_rule_type_id_price_list_id_unique",
|
||||
"columnNames": [
|
||||
"price_list_id",
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["price_list_id", "rule_type_id"],
|
||||
"composite": true,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "price_list_rule_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -989,25 +880,17 @@
|
||||
"foreignKeys": {
|
||||
"price_list_rule_rule_type_id_foreign": {
|
||||
"constraintName": "price_list_rule_rule_type_id_foreign",
|
||||
"columnNames": [
|
||||
"rule_type_id"
|
||||
],
|
||||
"columnNames": ["rule_type_id"],
|
||||
"localTableName": "public.price_list_rule",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.rule_type",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"price_list_rule_price_list_id_foreign": {
|
||||
"constraintName": "price_list_rule_price_list_id_foreign",
|
||||
"columnNames": [
|
||||
"price_list_id"
|
||||
],
|
||||
"columnNames": ["price_list_id"],
|
||||
"localTableName": "public.price_list_rule",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.price_list",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
@@ -1047,9 +930,7 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"price_list_rule_id"
|
||||
],
|
||||
"columnNames": ["price_list_rule_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_price_list_rule_price_list_rule_value_id",
|
||||
"primary": false,
|
||||
@@ -1057,9 +938,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "price_list_rule_value_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -1069,13 +948,9 @@
|
||||
"foreignKeys": {
|
||||
"price_list_rule_value_price_list_rule_id_foreign": {
|
||||
"constraintName": "price_list_rule_value_price_list_rule_id_foreign",
|
||||
"columnNames": [
|
||||
"price_list_rule_id"
|
||||
],
|
||||
"columnNames": ["price_list_rule_id"],
|
||||
"localTableName": "public.price_list_rule_value",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.price_list_rule",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
|
||||
@@ -18,7 +18,7 @@ export class Migration20230929122253 extends Migration {
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table "price_set_money_amount" ("id" text not null, "title" text not null, "price_set_id" text not null, "money_amount_id" text not null, "number_rules" integer not null default 0, constraint "price_set_money_amount_pkey" primary key ("id"));'
|
||||
'create table "price_set_money_amount" ("id" text not null, "title" text not null, "price_set_id" text not null, "money_amount_id" text not null, "rules_count" integer not null default 0, constraint "price_set_money_amount_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'create index "IDX_price_set_money_amount_price_set_id" on "price_set_money_amount" ("price_set_id");'
|
||||
@@ -103,7 +103,7 @@ export class Migration20230929122253 extends Migration {
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "price_list" ("id" text not null, "status" text check ("status" in (\'active\', \'draft\')) not null default \'draft\', "starts_at" timestamptz null, "ends_at" timestamptz null, "number_rules" integer not null default 0, constraint "price_list_pkey" primary key ("id"));'
|
||||
'create table if not exists "price_list" ("id" text not null, "status" text check ("status" in (\'active\', \'draft\')) not null default \'draft\', "starts_at" timestamptz null, "ends_at" timestamptz null, "rules_count" integer not null default 0, constraint "price_list_pkey" primary key ("id"));'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Migration20231101232834 extends Migration {
|
||||
|
||||
this.addSql(
|
||||
`ALTER TABLE price_list
|
||||
ADD COLUMN IF NOT EXISTS number_rules integer not null default 0`
|
||||
ADD COLUMN IF NOT EXISTS rules_count integer not null default 0`
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
@@ -65,7 +65,7 @@ export class Migration20231101232834 extends Migration {
|
||||
async down(): Promise<void> {
|
||||
this.addSql('drop table if exists "price_list_rule_value" cascade;')
|
||||
|
||||
this.addSql(`ALTER TABLE price_list DROP COLUMN IF EXISTS number_rules`)
|
||||
this.addSql(`ALTER TABLE price_list DROP COLUMN IF EXISTS rules_count`)
|
||||
|
||||
this.addSql('alter table "price_list" drop column if exists "title";')
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import RuleType from "./rule-type"
|
||||
type OptionalFields =
|
||||
| "status"
|
||||
| "type"
|
||||
| "number_rules"
|
||||
| "rules_count"
|
||||
| "starts_at"
|
||||
| "ends_at"
|
||||
| "created_at"
|
||||
@@ -82,7 +82,7 @@ export default class PriceList {
|
||||
rule_types = new Collection<RuleType>(this)
|
||||
|
||||
@Property({ columnType: "integer", default: 0 })
|
||||
number_rules?: number
|
||||
rules_count?: number
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
|
||||
@@ -38,7 +38,7 @@ export default class PriceSetMoneyAmount {
|
||||
money_amount?: MoneyAmount
|
||||
|
||||
@Property({ columnType: "integer", default: 0 })
|
||||
number_rules?: number
|
||||
rules_count?: number
|
||||
|
||||
@OneToMany({
|
||||
entity: () => PriceRule,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
PricingFilters,
|
||||
} from "@medusajs/types"
|
||||
import { MedusaError, MikroOrmBase } from "@medusajs/utils"
|
||||
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { PricingRepositoryService } from "../types"
|
||||
|
||||
@@ -67,9 +68,9 @@ export class PricingRepository
|
||||
id: "psma1.id",
|
||||
price_set_id: "psma1.price_set_id",
|
||||
money_amount_id: "psma1.money_amount_id",
|
||||
number_rules: "psma1.number_rules",
|
||||
rules_count: "psma1.rules_count",
|
||||
price_list_id: "psma1.price_list_id",
|
||||
pl_number_rules: "pl.number_rules",
|
||||
pl_rules_count: "pl.rules_count",
|
||||
pl_type: "pl.type",
|
||||
has_price_list: knex.raw(
|
||||
"case when psma1.price_list_id IS NULL then False else True end"
|
||||
@@ -86,23 +87,22 @@ export class PricingRepository
|
||||
)
|
||||
.leftJoin("rule_type as plrt", "plrt.id", "plr.rule_type_id")
|
||||
.leftJoin("rule_type as rt", "rt.id", "pr.rule_type_id")
|
||||
.orderBy("pl.number_rules", "desc")
|
||||
.orderBy("number_rules", "desc")
|
||||
.orderBy([
|
||||
{ column: "number_rules", order: "desc" },
|
||||
{ column: "pl.number_rules", order: "desc" },
|
||||
{ column: "rules_count", order: "desc" },
|
||||
{ column: "pl.rules_count", order: "desc" },
|
||||
])
|
||||
.groupBy("psma1.id", "pl.id")
|
||||
.having(
|
||||
knex.raw(
|
||||
"count(DISTINCT rt.rule_attribute) = psma1.number_rules AND psma1.price_list_id IS NULL"
|
||||
"count(DISTINCT rt.rule_attribute) = psma1.rules_count AND psma1.price_list_id IS NULL"
|
||||
)
|
||||
)
|
||||
.orHaving(
|
||||
knex.raw(
|
||||
"count(DISTINCT plrt.rule_attribute) = pl.number_rules AND psma1.price_list_id IS NOT NULL"
|
||||
"count(DISTINCT plrt.rule_attribute) = pl.rules_count AND psma1.price_list_id IS NOT NULL"
|
||||
)
|
||||
)
|
||||
|
||||
psmaSubQueryKnex.orWhere((q) => {
|
||||
for (const [key, value] of Object.entries(context)) {
|
||||
q.orWhere({
|
||||
@@ -110,8 +110,7 @@ export class PricingRepository
|
||||
"pr.value": value,
|
||||
})
|
||||
}
|
||||
|
||||
q.orWhere("psma1.number_rules", "=", 0)
|
||||
q.orWhere("psma1.rules_count", "=", 0)
|
||||
q.whereNull("psma1.price_list_id")
|
||||
})
|
||||
|
||||
@@ -124,14 +123,29 @@ export class PricingRepository
|
||||
this.whereNull("pl.ends_at").orWhere("pl.ends_at", ">=", date)
|
||||
})
|
||||
.andWhere(function () {
|
||||
for (const [key, value] of Object.entries(context)) {
|
||||
this.orWhere({
|
||||
"plrt.rule_attribute": key,
|
||||
})
|
||||
this.whereIn("plrv.value", [value])
|
||||
}
|
||||
this.andWhere(function () {
|
||||
for (const [key, value] of Object.entries(context)) {
|
||||
this.orWhere({
|
||||
"plrt.rule_attribute": key,
|
||||
})
|
||||
this.whereIn("plrv.value", [value])
|
||||
}
|
||||
|
||||
this.orWhere("pl.number_rules", "=", 0)
|
||||
this.orWhere("pl.rules_count", "=", 0)
|
||||
})
|
||||
|
||||
this.andWhere(function () {
|
||||
this.andWhere(function () {
|
||||
for (const [key, value] of Object.entries(context)) {
|
||||
this.orWhere({
|
||||
"rt.rule_attribute": key,
|
||||
"pr.value": value,
|
||||
})
|
||||
}
|
||||
this.andWhere("psma1.rules_count", ">", 0)
|
||||
})
|
||||
this.orWhere("psma1.rules_count", "=", 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -146,8 +160,8 @@ export class PricingRepository
|
||||
max_quantity: "ma.max_quantity",
|
||||
currency_code: "ma.currency_code",
|
||||
default_priority: "rt.default_priority",
|
||||
number_rules: "psma.number_rules",
|
||||
pl_number_rules: "psma.pl_number_rules",
|
||||
rules_count: "psma.rules_count",
|
||||
pl_rules_count: "psma.pl_rules_count",
|
||||
price_list_type: "psma.pl_type",
|
||||
price_list_id: "psma.price_list_id",
|
||||
})
|
||||
@@ -160,9 +174,9 @@ export class PricingRepository
|
||||
|
||||
.orderBy([
|
||||
{ column: "psma.has_price_list", order: "asc" },
|
||||
{ column: "number_rules", order: "desc" },
|
||||
{ column: "default_priority", order: "desc" },
|
||||
{ column: "amount", order: "asc" },
|
||||
{ column: "rules_count", order: "desc" },
|
||||
{ column: "default_priority", order: "desc" },
|
||||
])
|
||||
|
||||
if (quantity) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Context, DAL, FindConfig, PricingTypes } from "@medusajs/types"
|
||||
import {
|
||||
doNotForceTransaction,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
|
||||
@@ -14,12 +14,14 @@ import {
|
||||
RuleTypeDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
groupBy,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
PriceListType,
|
||||
arrayDifference,
|
||||
deduplicate,
|
||||
groupBy,
|
||||
removeNullish,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
@@ -153,7 +155,7 @@ export default class PricingModuleService<
|
||||
pricingFilters.id.map(
|
||||
(priceSetId: string): PricingTypes.CalculatedPriceSet => {
|
||||
// This is where we select prices, for now we just do a first match based on the database results
|
||||
// which is prioritized by number_rules first for exact match and then deafult_priority of the rule_type
|
||||
// which is prioritized by rules_count first for exact match and then deafult_priority of the rule_type
|
||||
// inject custom price selection here
|
||||
const prices = pricesSetPricesMap.get(priceSetId) || []
|
||||
const priceListPrice = prices.find((p) => p.price_list_id)
|
||||
@@ -288,7 +290,6 @@ export default class PricingModuleService<
|
||||
{ id: priceSets.filter((p) => !!p).map((p) => p!.id) },
|
||||
{
|
||||
relations: ["rule_types", "money_amounts", "price_rules"],
|
||||
take: null,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
@@ -332,7 +333,9 @@ export default class PricingModuleService<
|
||||
}
|
||||
|
||||
const invalidMoneyAmountRule = data
|
||||
.map((d) => d.prices?.map((ma) => Object.keys(ma.rules)).flat() ?? [])
|
||||
.map(
|
||||
(d) => d.prices?.map((ma) => Object.keys(ma?.rules ?? {})).flat() ?? []
|
||||
)
|
||||
.flat()
|
||||
.filter((r) => !ruleTypeMap.has(r))
|
||||
|
||||
@@ -360,6 +363,7 @@ export default class PricingModuleService<
|
||||
price_set: createdPriceSets[index],
|
||||
})) || []
|
||||
)
|
||||
|
||||
if (ruleTypeData.length > 0) {
|
||||
await this.priceSetRuleTypeService_.create(
|
||||
ruleTypeData as unknown as PricingTypes.CreatePriceSetRuleTypeDTO[],
|
||||
@@ -387,7 +391,7 @@ export default class PricingModuleService<
|
||||
price_set: createdPriceSets[index],
|
||||
money_amount: createdMoneyAmounts[moneyAmountIndex++],
|
||||
title: "test", // TODO: accept title
|
||||
number_rules: numberOfRules,
|
||||
rules_count: numberOfRules,
|
||||
}
|
||||
priceSetMoneyAmountData.push(priceSetMoneyAmount)
|
||||
|
||||
@@ -412,7 +416,7 @@ export default class PricingModuleService<
|
||||
|
||||
// Update price set money amount references
|
||||
for (let i = 0, j = 0; i < priceSetMoneyAmountData.length; i++) {
|
||||
const rulesCount = (priceSetMoneyAmountData[i] as any).number_rules
|
||||
const rulesCount = (priceSetMoneyAmountData[i] as any).rules_count
|
||||
for (let k = 0; k < rulesCount; k++, j++) {
|
||||
;(priceRulesData[j] as any).price_set_money_amount =
|
||||
createdPriceSetMoneyAmounts[i]
|
||||
@@ -449,19 +453,22 @@ export default class PricingModuleService<
|
||||
|
||||
const priceSets = await this.addRules_(inputs, sharedContext)
|
||||
|
||||
return (Array.isArray(data) ? priceSets : priceSets[0]) as unknown as
|
||||
| PricingTypes.PriceSetDTO[]
|
||||
| PricingTypes.PriceSetDTO
|
||||
return await this.list(
|
||||
{ id: priceSets.map(({ id }) => id) },
|
||||
{
|
||||
relations: ["rule_types"],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async addRules_(
|
||||
inputs: PricingTypes.AddRulesDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<PricingTypes.PriceSetDTO[]> {
|
||||
): Promise<TPriceSet[]> {
|
||||
const priceSets = await this.priceSetService_.list(
|
||||
{ id: inputs.map((d) => d.priceSetId) },
|
||||
{ relations: ["rule_types"], take: null },
|
||||
{ relations: ["rule_types"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -537,12 +544,7 @@ export default class PricingModuleService<
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return this.baseRepository_.serialize<PricingTypes.PriceSetDTO[]>(
|
||||
priceSets,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
return priceSets
|
||||
}
|
||||
|
||||
async addPrices(
|
||||
@@ -566,7 +568,7 @@ export default class PricingModuleService<
|
||||
|
||||
return (await this.list(
|
||||
{ id: input.map((d) => d.priceSetId) },
|
||||
{ relations: ["money_amounts"], take: null },
|
||||
{ relations: ["money_amounts"] },
|
||||
sharedContext
|
||||
)) as unknown as PricingTypes.PriceSetDTO[] | PricingTypes.PriceSetDTO
|
||||
}
|
||||
@@ -578,7 +580,7 @@ export default class PricingModuleService<
|
||||
) {
|
||||
const priceSets = await this.list(
|
||||
{ id: input.map((d) => d.priceSetId) },
|
||||
{ relations: ["rule_types"], take: null },
|
||||
{ relations: ["rule_types"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -640,7 +642,7 @@ export default class PricingModuleService<
|
||||
price_set: priceSetId,
|
||||
money_amount: ma,
|
||||
title: "test", // TODO: accept title
|
||||
number_rules: numberOfRules,
|
||||
rules_count: numberOfRules,
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -684,7 +686,7 @@ export default class PricingModuleService<
|
||||
{
|
||||
id: data.map((d) => d.id),
|
||||
},
|
||||
{ take: null },
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
const priceSetIds = priceSets.map((ps) => ps.id)
|
||||
@@ -1386,61 +1388,71 @@ export default class PricingModuleService<
|
||||
data: PricingTypes.CreatePriceListDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const createdPriceLists: PricingTypes.PriceListDTO[] = []
|
||||
const ruleAttributes = data
|
||||
.map((priceListData) => Object.keys(priceListData.rules || {}))
|
||||
.flat()
|
||||
const ruleTypeAttributes: string[] = []
|
||||
|
||||
for (const priceListData of data) {
|
||||
const { prices = [], rules: priceListRules = {} } = priceListData
|
||||
|
||||
ruleTypeAttributes.push(...Object.keys(priceListRules))
|
||||
|
||||
for (const price of prices) {
|
||||
const { rules: priceListPriceRules = {} } = price
|
||||
|
||||
ruleTypeAttributes.push(...Object.keys(priceListPriceRules))
|
||||
}
|
||||
}
|
||||
|
||||
const ruleTypes = await this.listRuleTypes(
|
||||
{
|
||||
rule_attribute: ruleAttributes,
|
||||
},
|
||||
{ rule_attribute: ruleTypeAttributes },
|
||||
{ take: null }
|
||||
)
|
||||
|
||||
const invalidRuleTypes = arrayDifference(
|
||||
deduplicate(ruleTypeAttributes),
|
||||
ruleTypes.map((ruleType) => ruleType.rule_attribute)
|
||||
)
|
||||
|
||||
if (invalidRuleTypes.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Cannot find RuleTypes with rule_attribute - ${invalidRuleTypes.join(
|
||||
", "
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
const ruleTypeMap: Map<string, RuleTypeDTO> = new Map(
|
||||
ruleTypes.map((rt) => [rt.rule_attribute, rt])
|
||||
)
|
||||
|
||||
const priceListsToCreate: PricingTypes.CreatePriceListDTO[] = []
|
||||
|
||||
for (const priceListData of data) {
|
||||
const { rules = {}, prices = [], ...priceListOnlyData } = priceListData
|
||||
const { rules = {}, ...priceListOnlyData } = priceListData
|
||||
|
||||
const [createdPriceList] = (await this.priceListService_.create(
|
||||
[
|
||||
{
|
||||
...priceListOnlyData,
|
||||
number_rules: Object.keys(rules).length,
|
||||
},
|
||||
],
|
||||
sharedContext
|
||||
)) as unknown as PricingTypes.PriceListDTO[]
|
||||
priceListsToCreate.push({
|
||||
...priceListOnlyData,
|
||||
rules_count: Object.keys(rules).length,
|
||||
})
|
||||
}
|
||||
|
||||
createdPriceLists.push(createdPriceList)
|
||||
const priceLists = (await this.priceListService_.create(
|
||||
priceListsToCreate
|
||||
)) as unknown as PricingTypes.PriceListDTO[]
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
const { rules = {}, prices = [] } = data[i]
|
||||
const priceList = priceLists[i]
|
||||
|
||||
for (const [ruleAttribute, ruleValues = []] of Object.entries(rules)) {
|
||||
// Find or create rule type
|
||||
let ruleType = ruleTypeMap.get(ruleAttribute)
|
||||
|
||||
if (!ruleType) {
|
||||
;[ruleType] = await this.createRuleTypes(
|
||||
[
|
||||
{
|
||||
name: ruleAttribute,
|
||||
rule_attribute: ruleAttribute,
|
||||
},
|
||||
],
|
||||
sharedContext
|
||||
)
|
||||
|
||||
ruleTypeMap.set(ruleAttribute, ruleType)
|
||||
}
|
||||
let ruleType = ruleTypeMap.get(ruleAttribute)!
|
||||
|
||||
// Create the rule
|
||||
const [priceListRule] = await this.priceListRuleService_.create(
|
||||
[
|
||||
{
|
||||
price_list: createdPriceList,
|
||||
rule_type: ruleType?.id || ruleType,
|
||||
price_list: priceList,
|
||||
rule_type: ruleType.id,
|
||||
},
|
||||
],
|
||||
sharedContext
|
||||
@@ -1461,29 +1473,48 @@ export default class PricingModuleService<
|
||||
}
|
||||
|
||||
for (const price of prices) {
|
||||
const { price_set_id: priceSetId, ...moneyAmountData } = price
|
||||
const {
|
||||
price_set_id: priceSetId,
|
||||
rules: priceRules = {},
|
||||
...moneyAmountData
|
||||
} = price
|
||||
|
||||
const [moneyAmount] = await this.moneyAmountService_.create(
|
||||
[moneyAmountData],
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.priceSetMoneyAmountService_.create(
|
||||
[
|
||||
{
|
||||
price_set: priceSetId,
|
||||
price_list: createdPriceList,
|
||||
money_amount: moneyAmount,
|
||||
title: "test",
|
||||
number_rules: 0,
|
||||
},
|
||||
] as unknown as PricingTypes.CreatePriceSetMoneyAmountDTO[],
|
||||
const [priceSetMoneyAmount] =
|
||||
await this.priceSetMoneyAmountService_.create(
|
||||
[
|
||||
{
|
||||
price_set: priceSetId,
|
||||
price_list: priceList,
|
||||
money_amount: moneyAmount,
|
||||
title: "test",
|
||||
rules_count: Object.keys(priceRules).length,
|
||||
},
|
||||
] as unknown as PricingTypes.CreatePriceSetMoneyAmountDTO[],
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.createPriceRules(
|
||||
Object.entries(priceRules).map(([ruleAttribute, ruleValue]) => {
|
||||
return {
|
||||
price_set_id: priceSetId,
|
||||
rule_type:
|
||||
ruleTypeMap.get(ruleAttribute)!?.id ||
|
||||
ruleTypeMap.get(ruleAttribute)!,
|
||||
value: ruleValue,
|
||||
price_set_money_amount: priceSetMoneyAmount as any,
|
||||
}
|
||||
}),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return createdPriceLists
|
||||
return priceLists
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
@@ -1519,7 +1550,7 @@ export default class PricingModuleService<
|
||||
|
||||
const existingPriceLists = await this.listPriceLists(
|
||||
{ id: priceListIds },
|
||||
{ relations: ["price_list_rules"], take: null },
|
||||
{ relations: ["price_list_rules"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -1531,7 +1562,7 @@ export default class PricingModuleService<
|
||||
{
|
||||
id: priceListRuleIds,
|
||||
},
|
||||
{ take: null },
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -1561,7 +1592,7 @@ export default class PricingModuleService<
|
||||
}
|
||||
|
||||
if (typeof rules === "object") {
|
||||
updatePriceListData.number_rules = Object.keys(rules).length
|
||||
updatePriceListData.rules_count = Object.keys(rules).length
|
||||
}
|
||||
|
||||
const [updatedPriceList] = (await this.priceListService_.update(
|
||||
@@ -1750,11 +1781,78 @@ export default class PricingModuleService<
|
||||
data: PricingTypes.AddPriceListPricesDTO[],
|
||||
sharedContext: Context = {}
|
||||
): Promise<PricingTypes.PriceListDTO[]> {
|
||||
const priceLists = await this.listPriceLists(
|
||||
{ id: data.map((d) => d.priceListId) },
|
||||
{
|
||||
take: null,
|
||||
const ruleTypeAttributes: string[] = []
|
||||
const priceListIds: string[] = []
|
||||
const priceSetIds: string[] = []
|
||||
|
||||
for (const priceListData of data) {
|
||||
priceListIds.push(priceListData.priceListId)
|
||||
|
||||
for (const price of priceListData.prices) {
|
||||
ruleTypeAttributes.push(...Object.keys(price.rules || {}))
|
||||
priceSetIds.push(price.price_set_id)
|
||||
}
|
||||
}
|
||||
|
||||
const ruleTypes = await this.listRuleTypes(
|
||||
{ rule_attribute: ruleTypeAttributes },
|
||||
{ take: null },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const priceSets = await this.list(
|
||||
{ id: priceSetIds },
|
||||
{ relations: ["rule_types"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const priceSetRuleTypeMap: Map<string, Set<string>> = priceSets.reduce(
|
||||
(acc, curr) => {
|
||||
const priceSetRuleAttributeSet: Set<string> =
|
||||
acc.get(curr.id) || new Set()
|
||||
|
||||
for (const rt of curr.rule_types ?? []) {
|
||||
priceSetRuleAttributeSet.add(rt.rule_attribute)
|
||||
}
|
||||
|
||||
acc.set(curr.id, priceSetRuleAttributeSet)
|
||||
return acc
|
||||
},
|
||||
new Map()
|
||||
)
|
||||
|
||||
const ruleTypeErrors: string[] = []
|
||||
|
||||
for (const priceListData of data) {
|
||||
for (const price of priceListData.prices) {
|
||||
for (const rule_attribute of Object.keys(price.rules ?? {})) {
|
||||
if (
|
||||
!priceSetRuleTypeMap.get(price.price_set_id)?.has(rule_attribute)
|
||||
) {
|
||||
ruleTypeErrors.push(
|
||||
`rule_attribute "${rule_attribute}" in price set ${price.price_set_id}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ruleTypeErrors.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Invalid rule type configuration: Price set rules doesn't exist for ${ruleTypeErrors.join(
|
||||
", "
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
const ruleTypeMap: Map<string, RuleTypeDTO> = new Map(
|
||||
ruleTypes.map((rt) => [rt.rule_attribute, rt])
|
||||
)
|
||||
|
||||
const priceLists = await this.listPriceLists(
|
||||
{ id: priceListIds },
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -1772,23 +1870,41 @@ export default class PricingModuleService<
|
||||
|
||||
await Promise.all(
|
||||
prices.map(async (price) => {
|
||||
const priceRules = price.rules || {}
|
||||
const noOfRules = Object.keys(priceRules).length
|
||||
|
||||
const [moneyAmount] = await this.moneyAmountService_.create(
|
||||
[price] as unknown as CreateMoneyAmountDTO[],
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const psma = await this.priceSetMoneyAmountService_.create(
|
||||
const [psma] = await this.priceSetMoneyAmountService_.create(
|
||||
[
|
||||
{
|
||||
price_set: price.price_set_id,
|
||||
money_amount: moneyAmount.id,
|
||||
title: "test",
|
||||
price_list: priceList.id,
|
||||
rules_count: noOfRules,
|
||||
},
|
||||
],
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.createPriceRules(
|
||||
Object.entries(priceRules).map(([ruleAttribute, ruleValue]) => {
|
||||
return {
|
||||
price_set_id: price.price_set_id,
|
||||
rule_type:
|
||||
ruleTypeMap.get(ruleAttribute)!?.id ||
|
||||
ruleTypeMap.get(ruleAttribute)!,
|
||||
value: ruleValue,
|
||||
price_set_money_amount: psma as any,
|
||||
}
|
||||
}),
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return psma
|
||||
})
|
||||
)
|
||||
@@ -1816,7 +1932,6 @@ export default class PricingModuleService<
|
||||
{ id: data.map((d) => d.priceListId) },
|
||||
{
|
||||
relations: ["price_list_rules", "price_list_rules.rule_type"],
|
||||
take: null,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
@@ -1956,7 +2071,6 @@ export default class PricingModuleService<
|
||||
{ id: data.map((d) => d.priceListId) },
|
||||
{
|
||||
relations: ["price_list_rules", "price_list_rules.rule_type"],
|
||||
take: null,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -7,5 +7,5 @@ export interface CreatePriceListDTO {
|
||||
ends_at?: string
|
||||
status?: PriceListStatus
|
||||
type?: PriceListType
|
||||
number_rules?: number
|
||||
rules_count?: number
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export interface PriceListDTO {
|
||||
/**
|
||||
* The number of rules associated with this price list.
|
||||
*/
|
||||
number_rules?: number
|
||||
rules_count?: number
|
||||
/**
|
||||
* The associated price set money amounts.
|
||||
*
|
||||
@@ -111,8 +111,22 @@ export interface PriceListPriceDTO extends CreateMoneyAmountDTO {
|
||||
* The ID of the associated price set.
|
||||
*/
|
||||
price_set_id: string
|
||||
/**
|
||||
* The rules to add to the price. The object's keys are rule types' `rule_attribute` attribute, and values are the value of that rule associated with this price.
|
||||
*/
|
||||
rules?: CreatePriceSetPriceRules
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The price rules to be set for each price in the price set.
|
||||
*
|
||||
* Each key of the object is a rule type's `rule_attribute`, and its value
|
||||
* is the values of the rule.
|
||||
*/
|
||||
export interface CreatePriceSetPriceRules extends Record<string, string> {}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
@@ -154,7 +168,7 @@ export interface CreatePriceListDTO {
|
||||
/**
|
||||
* The number of rules associated with the price list.
|
||||
*/
|
||||
number_rules?: number
|
||||
rules_count?: number
|
||||
/**
|
||||
* The rules to be created and associated with the price list.
|
||||
*/
|
||||
@@ -194,7 +208,7 @@ export interface UpdatePriceListDTO {
|
||||
/**
|
||||
* The number of rules associated with the price list.
|
||||
*/
|
||||
number_rules?: number
|
||||
rules_count?: number
|
||||
/**
|
||||
* The rules to be created and associated with the price list.
|
||||
*/
|
||||
@@ -227,7 +241,7 @@ export interface FilterablePriceListProps
|
||||
/**
|
||||
* The number of rules to filter price lists by.
|
||||
*/
|
||||
number_rules?: number[]
|
||||
rules_count?: number[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BaseFilterable } from "../../dal"
|
||||
import { PriceSetDTO } from "./price-set"
|
||||
import { PriceSetMoneyAmountDTO } from "./price-set-money-amount"
|
||||
import { RuleTypeDTO } from "./rule-type"
|
||||
|
||||
/**
|
||||
@@ -56,24 +57,38 @@ export interface PriceRuleDTO {
|
||||
* A price rule to create.
|
||||
*/
|
||||
export interface CreatePriceRuleDTO {
|
||||
/**
|
||||
* The ID of the price rule.
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* The ID of the associated price set.
|
||||
*/
|
||||
price_set_id: string
|
||||
price_set_id?: string
|
||||
/**
|
||||
* The ID or object of the associated price set.
|
||||
*/
|
||||
price_set?: string | PriceSetDTO
|
||||
/**
|
||||
* The ID of the associated rule type.
|
||||
*/
|
||||
rule_type_id: string
|
||||
rule_type_id?: string
|
||||
/**
|
||||
* The ID of the associated rule type.
|
||||
*/
|
||||
rule_type?: string | RuleTypeDTO
|
||||
/**
|
||||
* The value of the price rule.
|
||||
*/
|
||||
value: string
|
||||
/**
|
||||
* The priority of the price rule in comparison to other applicable price rules.
|
||||
*/
|
||||
priority?: number
|
||||
price_set_money_amount_id: string
|
||||
/**
|
||||
* The ID of the associated price set money amount.
|
||||
*/
|
||||
price_set_money_amount_id?: string
|
||||
/**
|
||||
* The ID or object of the associated price set money amount.
|
||||
*/
|
||||
price_set_money_amount?: string | PriceSetMoneyAmountDTO
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,13 +20,13 @@ export interface PriceSetMoneyAmountDTO {
|
||||
title?: string
|
||||
/**
|
||||
* The price set associated with the price set money amount.
|
||||
*
|
||||
*
|
||||
* @expandable
|
||||
*/
|
||||
price_set?: PriceSetDTO
|
||||
/**
|
||||
* The price list associated with the price set money amount.
|
||||
*
|
||||
*
|
||||
* @expandable
|
||||
*/
|
||||
price_list?: PriceListDTO
|
||||
@@ -36,13 +36,13 @@ export interface PriceSetMoneyAmountDTO {
|
||||
price_set_id?: string
|
||||
/**
|
||||
* The price rules associated with the price set money amount.
|
||||
*
|
||||
*
|
||||
* @expandable
|
||||
*/
|
||||
price_rules?: PriceRuleDTO[]
|
||||
/**
|
||||
* The money amount associated with the price set money amount.
|
||||
*
|
||||
*
|
||||
* @expandable
|
||||
*/
|
||||
money_amount?: MoneyAmountDTO
|
||||
@@ -60,6 +60,7 @@ export interface CreatePriceSetMoneyAmountDTO {
|
||||
price_set?: PriceSetDTO | string
|
||||
price_list?: PriceListDTO | string
|
||||
money_amount?: MoneyAmountDTO | string
|
||||
rules_count?: number
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BaseFilterable } from "../../dal";
|
||||
import { CreateMoneyAmountDTO, FilterableMoneyAmountProps, MoneyAmountDTO } from "./money-amount";
|
||||
import { RuleTypeDTO } from "./rule-type";
|
||||
import { CreatePriceSetPriceRules } from "./price-list";
|
||||
|
||||
/**
|
||||
* @interface
|
||||
@@ -52,7 +53,7 @@ export interface PriceSetDTO {
|
||||
* @interface
|
||||
*
|
||||
* A calculated price set's data.
|
||||
*
|
||||
*
|
||||
* @privateRemarks
|
||||
* Do we still need this type? Shouldn't we use CalculatedPriceSet instead?
|
||||
*/
|
||||
@@ -93,7 +94,7 @@ export interface CalculatedPriceSetDTO {
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
*
|
||||
* The calculated price for a specific price set and context.
|
||||
*/
|
||||
export interface CalculatedPriceSet {
|
||||
@@ -102,7 +103,7 @@ export interface CalculatedPriceSet {
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* Whether the calculated price is associated with a price list. During the calculation process, if no valid price list is found,
|
||||
* Whether the calculated price is associated with a price list. During the calculation process, if no valid price list is found,
|
||||
* the calculated price is set to the original price, which doesn't belong to a price list. In that case, the value of this property is `false`.
|
||||
*/
|
||||
is_calculated_price_price_list?: boolean
|
||||
@@ -112,7 +113,7 @@ export interface CalculatedPriceSet {
|
||||
calculated_amount: number | null
|
||||
|
||||
/**
|
||||
* Whether the original price is associated with a price list. During the calculation process, if the price list of the calculated price is of type override,
|
||||
* Whether the original price is associated with a price list. During the calculation process, if the price list of the calculated price is of type override,
|
||||
* the original price will be the same as the calculated price. In that case, the value of this property is `true`.
|
||||
*/
|
||||
is_original_price_price_list?: boolean
|
||||
@@ -192,7 +193,7 @@ export interface AddRulesDTO {
|
||||
/**
|
||||
* The rules to add to a price set.
|
||||
*/
|
||||
rules: {
|
||||
rules: {
|
||||
/**
|
||||
* The value of the rule's `rule_attribute` attribute.
|
||||
*/
|
||||
@@ -209,7 +210,7 @@ export interface CreatePricesDTO extends CreateMoneyAmountDTO {
|
||||
/**
|
||||
* The rules to add to the price. The object's keys are rule types' `rule_attribute` attribute, and values are the value of that rule associated with this price.
|
||||
*/
|
||||
rules: Record<string, string>
|
||||
rules?: CreatePriceSetPriceRules
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,7 +254,7 @@ export interface CreatePriceSetDTO {
|
||||
/**
|
||||
* The rules to associate with the price set.
|
||||
*/
|
||||
rules?: {
|
||||
rules?: {
|
||||
/**
|
||||
* the value of the rule's `rule_attribute` attribute.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface CreatePriceListDTO {
|
||||
starts_at?: string
|
||||
ends_at?: string
|
||||
status?: PriceListStatus
|
||||
number_rules?: number
|
||||
rules_count?: number
|
||||
rules?: PriceListRuleDTO[]
|
||||
prices?: {
|
||||
amount: number
|
||||
@@ -63,7 +63,7 @@ export interface CreatePriceListWorkflowDTO {
|
||||
starts_at?: string
|
||||
ends_at?: string
|
||||
status?: PriceListStatus
|
||||
number_rules?: number
|
||||
rules_count?: number
|
||||
prices: InputPrice[]
|
||||
rules?: CreatePriceListRules
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user