chore(): Further improve promotions computation (#13556)
* chore(): Further improve promotions * chore(): Further improve promotions * chore(): Further improve promotions * chore(): Further improve promotions * chore(): Further improve promotions * Create lazy-lemons-occur.md * chore(): Further improve promotions * WIP * fix * improve: * fix attribute tests * fix tests * union
This commit is contained in:
1 parent
cb716856b6
commit
4c1c1dd4c0
12 files changed
+429
-159
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/promotion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Chore(): Further promotions pre filtering improvements
|
||||||
@@ -140,7 +140,7 @@ medusaIntegrationTestRunner({
|
|||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_id",
|
attribute: "items.product_id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: [product.id],
|
values: [product.id],
|
||||||
},
|
},
|
||||||
@@ -2571,7 +2571,7 @@ medusaIntegrationTestRunner({
|
|||||||
apply_to_quantity: 1,
|
apply_to_quantity: 1,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_id",
|
attribute: "items.product_id",
|
||||||
operator: PromotionRuleOperator.IN,
|
operator: PromotionRuleOperator.IN,
|
||||||
values: [product.id],
|
values: [product.id],
|
||||||
},
|
},
|
||||||
@@ -4353,7 +4353,7 @@ medusaIntegrationTestRunner({
|
|||||||
currency_code: "eur",
|
currency_code: "eur",
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_id",
|
attribute: "items.product_id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: [product.id],
|
values: [product.id],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ medusaIntegrationTestRunner({
|
|||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_id",
|
attribute: "items.product_id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: "prod_mat",
|
values: "prod_mat",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1418,7 +1418,7 @@ medusaIntegrationTestRunner({
|
|||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_id",
|
attribute: "items.product_id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: [product.id],
|
values: [product.id],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ medusaIntegrationTestRunner({
|
|||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_id",
|
attribute: "items.product_id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: "prod_tshirt",
|
values: "prod_tshirt",
|
||||||
},
|
},
|
||||||
@@ -83,7 +83,7 @@ medusaIntegrationTestRunner({
|
|||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_id",
|
attribute: "items.product_id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: "prod_tshirt",
|
values: "prod_tshirt",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -469,7 +469,7 @@ medusaIntegrationTestRunner({
|
|||||||
max_quantity: 100,
|
max_quantity: 100,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "variant_id",
|
attribute: "items.variant_id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: [product.variants[0].id, product_2.variants[0].id],
|
values: [product.variants[0].id, product_2.variants[0].id],
|
||||||
},
|
},
|
||||||
@@ -877,7 +877,7 @@ medusaIntegrationTestRunner({
|
|||||||
max_quantity: 100,
|
max_quantity: 100,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "variant_id",
|
attribute: "items.variant_id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: [product.variants[0].id],
|
values: [product.variants[0].id],
|
||||||
},
|
},
|
||||||
|
|||||||
+292
-103
File diff suppressed because it is too large.
Load diff
+24
-24
@@ -266,7 +266,7 @@ moduleIntegrationTestRunner({
|
|||||||
const createdPromotion = await createDefaultPromotion(service, {
|
const createdPromotion = await createDefaultPromotion(service, {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: ["VIP", "top100"],
|
values: ["VIP", "top100"],
|
||||||
},
|
},
|
||||||
@@ -285,7 +285,7 @@ moduleIntegrationTestRunner({
|
|||||||
type: "standard",
|
type: "standard",
|
||||||
rules: [
|
rules: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: expect.arrayContaining([
|
values: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -305,7 +305,7 @@ moduleIntegrationTestRunner({
|
|||||||
const createdPromotion = await createDefaultPromotion(service, {
|
const createdPromotion = await createDefaultPromotion(service, {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: "VIP",
|
values: "VIP",
|
||||||
},
|
},
|
||||||
@@ -324,7 +324,7 @@ moduleIntegrationTestRunner({
|
|||||||
type: "standard",
|
type: "standard",
|
||||||
rules: [
|
rules: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: expect.arrayContaining([
|
values: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -469,14 +469,14 @@ moduleIntegrationTestRunner({
|
|||||||
buy_rules_min_quantity: 1,
|
buy_rules_min_quantity: 1,
|
||||||
buy_rules: [
|
buy_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_collection.id",
|
attribute: "items.product_collection.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: ["pcol_towel"],
|
values: ["pcol_towel"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product.id",
|
attribute: "items.product.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: ["prod_mat"],
|
values: ["prod_mat"],
|
||||||
},
|
},
|
||||||
@@ -498,14 +498,14 @@ moduleIntegrationTestRunner({
|
|||||||
allocation: "each",
|
allocation: "each",
|
||||||
buy_rules: [
|
buy_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_collection.id",
|
attribute: "items.product_collection.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: ["pcol_towel"],
|
values: ["pcol_towel"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product.id",
|
attribute: "items.product.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: ["prod_mat"],
|
values: ["prod_mat"],
|
||||||
},
|
},
|
||||||
@@ -528,14 +528,14 @@ moduleIntegrationTestRunner({
|
|||||||
buy_rules_min_quantity: 1,
|
buy_rules_min_quantity: 1,
|
||||||
buy_rules: [
|
buy_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_collection.id",
|
attribute: "items.product_collection.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: ["pcol_towel"],
|
values: ["pcol_towel"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product.id",
|
attribute: "items.product.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: "prod_mat",
|
values: "prod_mat",
|
||||||
},
|
},
|
||||||
@@ -551,14 +551,14 @@ moduleIntegrationTestRunner({
|
|||||||
buy_rules_min_quantity: 1,
|
buy_rules_min_quantity: 1,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
attribute: "product.id",
|
attribute: "items.product.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: [expect.objectContaining({ value: "prod_mat" })],
|
values: [expect.objectContaining({ value: "prod_mat" })],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
buy_rules: [
|
buy_rules: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
attribute: "product_collection.id",
|
attribute: "items.product_collection.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: [expect.objectContaining({ value: "pcol_towel" })],
|
values: [expect.objectContaining({ value: "pcol_towel" })],
|
||||||
}),
|
}),
|
||||||
@@ -980,7 +980,7 @@ moduleIntegrationTestRunner({
|
|||||||
it("should successfully add rules to a promotion", async () => {
|
it("should successfully add rules to a promotion", async () => {
|
||||||
const promotionRules = await service.addPromotionRules(promotion.id, [
|
const promotionRules = await service.addPromotionRules(promotion.id, [
|
||||||
{
|
{
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: ["VIP", "top100"],
|
values: ["VIP", "top100"],
|
||||||
},
|
},
|
||||||
@@ -989,7 +989,7 @@ moduleIntegrationTestRunner({
|
|||||||
expect(promotionRules).toEqual([
|
expect(promotionRules).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: promotionRules[0].id,
|
id: promotionRules[0].id,
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: expect.arrayContaining([
|
values: expect.arrayContaining([
|
||||||
expect.objectContaining({ value: "VIP" }),
|
expect.objectContaining({ value: "VIP" }),
|
||||||
@@ -1041,7 +1041,7 @@ moduleIntegrationTestRunner({
|
|||||||
promotion.id,
|
promotion.id,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: ["VIP", "top100"],
|
values: ["VIP", "top100"],
|
||||||
},
|
},
|
||||||
@@ -1051,7 +1051,7 @@ moduleIntegrationTestRunner({
|
|||||||
expect(promotionRules).toEqual([
|
expect(promotionRules).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: promotionRules[0].id,
|
id: promotionRules[0].id,
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: expect.arrayContaining([
|
values: expect.arrayContaining([
|
||||||
expect.objectContaining({ value: "VIP" }),
|
expect.objectContaining({ value: "VIP" }),
|
||||||
@@ -1075,14 +1075,14 @@ moduleIntegrationTestRunner({
|
|||||||
buy_rules_min_quantity: 1,
|
buy_rules_min_quantity: 1,
|
||||||
buy_rules: [
|
buy_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_collection.id",
|
attribute: "items.product_collection.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: ["pcol_towel"],
|
values: ["pcol_towel"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product.id",
|
attribute: "items.product.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: ["prod_1", "prod_2"],
|
values: ["prod_1", "prod_2"],
|
||||||
},
|
},
|
||||||
@@ -1125,7 +1125,7 @@ moduleIntegrationTestRunner({
|
|||||||
promotion.id,
|
promotion.id,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
attribute: "product.id",
|
attribute: "items.product.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: ["prod_3", "prod_4"],
|
values: ["prod_3", "prod_4"],
|
||||||
},
|
},
|
||||||
@@ -1135,7 +1135,7 @@ moduleIntegrationTestRunner({
|
|||||||
expect(promotionRules).toEqual([
|
expect(promotionRules).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: promotionRules[0].id,
|
id: promotionRules[0].id,
|
||||||
attribute: "product.id",
|
attribute: "items.product.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: expect.arrayContaining([
|
values: expect.arrayContaining([
|
||||||
expect.objectContaining({ value: "prod_3" }),
|
expect.objectContaining({ value: "prod_3" }),
|
||||||
@@ -1153,7 +1153,7 @@ moduleIntegrationTestRunner({
|
|||||||
promotion = await createDefaultPromotion(service, {
|
promotion = await createDefaultPromotion(service, {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: ["VIP", "top100"],
|
values: ["VIP", "top100"],
|
||||||
},
|
},
|
||||||
@@ -1219,7 +1219,7 @@ moduleIntegrationTestRunner({
|
|||||||
application_method: {
|
application_method: {
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "customer_group_id",
|
attribute: "customer.customer_group.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: ["VIP", "top100"],
|
values: ["VIP", "top100"],
|
||||||
},
|
},
|
||||||
@@ -1295,14 +1295,14 @@ moduleIntegrationTestRunner({
|
|||||||
buy_rules_min_quantity: 1,
|
buy_rules_min_quantity: 1,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product.id",
|
attribute: "items.product.id",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
values: ["prod_1", "prod_2"],
|
values: ["prod_1", "prod_2"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
buy_rules: [
|
buy_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_collection",
|
attribute: "items.product_collection.id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: ["pcol_towel"],
|
values: ["pcol_towel"],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -850,6 +850,15 @@
|
|||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_rule_attribute_operator\" ON \"promotion_rule\" (attribute, operator) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_rule_attribute_operator\" ON \"promotion_rule\" (attribute, operator) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_promotion_rule_attribute_operator_id",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"constraint": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_rule_attribute_operator_id\" ON \"promotion_rule\" (operator, attribute, id) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"keyName": "promotion_rule_pkey",
|
"keyName": "promotion_rule_pkey",
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
|
export class Migration20250919122137 extends Migration {
|
||||||
|
|
||||||
|
override async up(): Promise<void> {
|
||||||
|
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_promotion_rule_attribute_operator_id" ON "promotion_rule" (operator, attribute, id) WHERE deleted_at IS NULL;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async down(): Promise<void> {
|
||||||
|
this.addSql(`drop index if exists "IDX_promotion_rule_attribute_operator_id";`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -36,6 +36,12 @@ const PromotionRule = model
|
|||||||
unique: false,
|
unique: false,
|
||||||
where: "deleted_at IS NULL",
|
where: "deleted_at IS NULL",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "IDX_promotion_rule_attribute_operator_id",
|
||||||
|
on: ["operator", "attribute", "id"],
|
||||||
|
unique: false,
|
||||||
|
where: "deleted_at IS NULL",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
.cascades({
|
.cascades({
|
||||||
delete: ["values"],
|
delete: ["values"],
|
||||||
|
|||||||
+71
-23
@@ -69,7 +69,6 @@ export async function buildPromotionRuleQueryFilterFromContext(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// count the number of attributes in the map
|
|
||||||
const numberOfAttributes = attributeValueMap.size
|
const numberOfAttributes = attributeValueMap.size
|
||||||
if (numberOfAttributes > 10) {
|
if (numberOfAttributes > 10) {
|
||||||
const manager = (sharedContext.transactionManager ??
|
const manager = (sharedContext.transactionManager ??
|
||||||
@@ -96,18 +95,9 @@ export async function buildPromotionRuleQueryFilterFromContext(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build conditions for a NOT EXISTS subquery to exclude promotions with unsatisfiable rules
|
|
||||||
const sqlConditions: string[] = []
|
const sqlConditions: string[] = []
|
||||||
|
|
||||||
// First, check for rules where the attribute doesn't exist in context at all
|
// For each attribute, check if the values don't satisfy the rules
|
||||||
// These rules can never be satisfied
|
|
||||||
sqlConditions.push(
|
|
||||||
`pr.attribute NOT IN (${Array.from(attributeValueMap.keys())
|
|
||||||
.map((attr) => `'${attr.replace(/'/g, "''")}'`)
|
|
||||||
.join(",")})`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Then, for attributes that exist in context, check if the values don't satisfy the rules
|
|
||||||
attributeValueMap.forEach((valueSet, attribute) => {
|
attributeValueMap.forEach((valueSet, attribute) => {
|
||||||
const values = Array.from(valueSet)
|
const values = Array.from(valueSet)
|
||||||
const stringValues = values
|
const stringValues = values
|
||||||
@@ -121,7 +111,6 @@ export async function buildPromotionRuleQueryFilterFromContext(
|
|||||||
})
|
})
|
||||||
.filter((v) => v !== null) as number[]
|
.filter((v) => v !== null) as number[]
|
||||||
|
|
||||||
// Escape attribute name to prevent SQL injection
|
|
||||||
const escapedAttribute = `'${attribute.replace(/'/g, "''")}'`
|
const escapedAttribute = `'${attribute.replace(/'/g, "''")}'`
|
||||||
|
|
||||||
// For 'in' and 'eq' operators - rule is unsatisfiable if NO rule values overlap with context
|
// For 'in' and 'eq' operators - rule is unsatisfiable if NO rule values overlap with context
|
||||||
@@ -165,17 +154,25 @@ export async function buildPromotionRuleQueryFilterFromContext(
|
|||||||
// Handle the case where context has no attributes at all, it means
|
// Handle the case where context has no attributes at all, it means
|
||||||
// that any promotion that have a rule cant be satisfied by the context
|
// that any promotion that have a rule cant be satisfied by the context
|
||||||
if (attributeValueMap.size === 0) {
|
if (attributeValueMap.size === 0) {
|
||||||
// If context has no attributes, exclude all promotions that have any rules
|
// If context has no attributes, exclude all promotions that have any rules (promotion rules, target rules, or buy rules)
|
||||||
const notExistsSubquery = (alias: string) =>
|
const noRulesSubquery = (alias: string) =>
|
||||||
`
|
`
|
||||||
NOT EXISTS (
|
${alias}.id NOT IN (
|
||||||
SELECT 1 FROM promotion_promotion_rule ppr
|
SELECT DISTINCT ppr.promotion_id
|
||||||
WHERE ppr.promotion_id = ${alias}.id
|
FROM promotion_promotion_rule ppr
|
||||||
|
UNION
|
||||||
|
SELECT DISTINCT am.promotion_id
|
||||||
|
FROM promotion_application_method am
|
||||||
|
JOIN application_method_target_rules amtr ON am.id = amtr.application_method_id
|
||||||
|
UNION
|
||||||
|
SELECT DISTINCT am2.promotion_id
|
||||||
|
FROM promotion_application_method am2
|
||||||
|
JOIN application_method_buy_rules ambr ON am2.id = ambr.application_method_id
|
||||||
)
|
)
|
||||||
`.trim()
|
`.trim()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[raw((alias) => notExistsSubquery(alias))]: true,
|
[raw((alias) => noRulesSubquery(alias))]: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,18 +185,69 @@ export async function buildPromotionRuleQueryFilterFromContext(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const notExistsSubquery = (alias: string) =>
|
const attributeKeys = Array.from(attributeValueMap.keys())
|
||||||
|
.map((attr) => `'${attr.replace(/'/g, "''")}'`)
|
||||||
|
.join(",")
|
||||||
|
|
||||||
|
// Use anti-join approach for better performance than NOT IN
|
||||||
|
const antiJoinSubquery = (alias: string) =>
|
||||||
`
|
`
|
||||||
NOT EXISTS (
|
NOT EXISTS (
|
||||||
SELECT 1 FROM promotion_promotion_rule ppr
|
SELECT 1 FROM (
|
||||||
|
-- Promotions with rules for attributes not in context
|
||||||
|
SELECT ppr.promotion_id
|
||||||
|
FROM promotion_promotion_rule ppr
|
||||||
|
JOIN promotion_rule pr ON ppr.promotion_rule_id = pr.id
|
||||||
|
WHERE pr.attribute NOT IN (${attributeKeys})
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT am.promotion_id
|
||||||
|
FROM promotion_application_method am
|
||||||
|
JOIN application_method_target_rules amtr ON am.id = amtr.application_method_id
|
||||||
|
JOIN promotion_rule pr ON amtr.promotion_rule_id = pr.id
|
||||||
|
WHERE pr.attribute NOT IN (${attributeKeys})
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT am2.promotion_id
|
||||||
|
FROM promotion_application_method am2
|
||||||
|
JOIN application_method_buy_rules ambr ON am2.id = ambr.application_method_id
|
||||||
|
JOIN promotion_rule pr ON ambr.promotion_rule_id = pr.id
|
||||||
|
WHERE pr.attribute NOT IN (${attributeKeys})
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
-- Promotions with unsatisfiable rules for context attributes
|
||||||
|
SELECT ppr.promotion_id
|
||||||
|
FROM promotion_promotion_rule ppr
|
||||||
JOIN promotion_rule pr ON ppr.promotion_rule_id = pr.id
|
JOIN promotion_rule pr ON ppr.promotion_rule_id = pr.id
|
||||||
LEFT JOIN promotion_rule_value prv ON prv.promotion_rule_id = pr.id
|
LEFT JOIN promotion_rule_value prv ON prv.promotion_rule_id = pr.id
|
||||||
WHERE ppr.promotion_id = ${alias}.id
|
WHERE pr.attribute IN (${attributeKeys}) AND (${joinedConditions})
|
||||||
AND (${joinedConditions})
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT am.promotion_id
|
||||||
|
FROM promotion_application_method am
|
||||||
|
JOIN application_method_target_rules amtr ON am.id = amtr.application_method_id
|
||||||
|
JOIN promotion_rule pr ON amtr.promotion_rule_id = pr.id
|
||||||
|
LEFT JOIN promotion_rule_value prv ON prv.promotion_rule_id = pr.id
|
||||||
|
WHERE pr.attribute IN (${attributeKeys}) AND (${joinedConditions})
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT am2.promotion_id
|
||||||
|
FROM promotion_application_method am2
|
||||||
|
JOIN application_method_buy_rules ambr ON am2.id = ambr.application_method_id
|
||||||
|
JOIN promotion_rule pr ON ambr.promotion_rule_id = pr.id
|
||||||
|
LEFT JOIN promotion_rule_value prv ON prv.promotion_rule_id = pr.id
|
||||||
|
WHERE pr.attribute IN (${attributeKeys}) AND (${joinedConditions})
|
||||||
|
) excluded_promotions
|
||||||
|
WHERE excluded_promotions.promotion_id = ${alias}.id
|
||||||
)
|
)
|
||||||
`.trim()
|
`.trim()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[raw((alias) => notExistsSubquery(alias))]: true,
|
[raw((alias) => antiJoinSubquery(alias))]: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user