chore(): improve cart operations + Mikro orm 6.4.16 (#13712)
* chore(): Mikro orm 6.4.16 * Create small-ghosts-draw.md * update config * update config * fix delete * update config * update workflows * order improvements * test pricing quuery * test pricing quuery * configurable connection options * configurable connection options * configurable connection options * Update packages/modules/pricing/src/models/price.ts Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
76bf364440
commit
c54c5ed6de
@@ -136,6 +136,15 @@
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_deleted_at\" ON \"price_list\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_price_list_id_status_starts_at_ends_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_id_status_starts_at_ends_at\" ON \"price_list\" (id, status, starts_at, ends_at) WHERE deleted_at IS NULL AND status = 'active'"
|
||||
},
|
||||
{
|
||||
"keyName": "price_list_pkey",
|
||||
"columnNames": [
|
||||
@@ -252,6 +261,15 @@
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_attribute\" ON \"price_list_rule\" (attribute) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_price_list_rule_value",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_value\" ON \"price_list_rule\" USING gin (value) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "price_list_rule_pkey",
|
||||
"columnNames": [
|
||||
@@ -625,6 +643,15 @@
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_currency_code\" ON \"price\" (currency_code) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_price_currency_code_price_set_id_min_quantity",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_currency_code_price_set_id_min_quantity\" ON \"price\" (currency_code, price_set_id, min_quantity) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "price_pkey",
|
||||
"columnNames": [
|
||||
@@ -822,6 +849,15 @@
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_operator_value\" ON \"price_rule\" (operator, value) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_price_rule_attribute_value_price_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_attribute_value_price_id\" ON \"price_rule\" (attribute, value, price_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "price_rule_pkey",
|
||||
"columnNames": [
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20251009110625 extends Migration {
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(
|
||||
`CREATE INDEX IF NOT EXISTS "IDX_price_list_id_status_starts_at_ends_at" ON "price_list" (id, status, starts_at, ends_at) WHERE deleted_at IS NULL AND status = 'active';`
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
`CREATE INDEX IF NOT EXISTS "IDX_price_list_rule_value" ON "price_list_rule" USING gin (value) WHERE deleted_at IS NULL;`
|
||||
)
|
||||
this.addSql(
|
||||
`CREATE INDEX IF NOT EXISTS "IDX_price_rule_attribute_value_price_id" ON "price_rule" (attribute, value, price_id) WHERE deleted_at IS NULL;`
|
||||
)
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(
|
||||
`drop index if exists "IDX_price_list_id_status_starts_at_ends_at";`
|
||||
)
|
||||
|
||||
this.addSql(`drop index if exists "IDX_price_list_rule_value";`)
|
||||
|
||||
this.addSql(
|
||||
`drop index if exists "IDX_price_rule_attribute_value_price_id";`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,11 @@ const PriceListRule = model
|
||||
on: ["attribute"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["value"],
|
||||
where: "deleted_at IS NULL",
|
||||
type: "gin",
|
||||
},
|
||||
])
|
||||
|
||||
export default PriceListRule
|
||||
|
||||
@@ -26,5 +26,11 @@ const PriceList = model
|
||||
.cascades({
|
||||
delete: ["price_list_rules", "prices"],
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
on: ["id", "status", "starts_at", "ends_at"],
|
||||
where: "deleted_at IS NULL AND status = 'active'",
|
||||
},
|
||||
])
|
||||
|
||||
export default PriceList
|
||||
|
||||
@@ -30,6 +30,10 @@ const PriceRule = model
|
||||
on: ["operator", "value"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["attribute", "value", "price_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
export default PriceRule
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
PriceListStatus,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
import {
|
||||
Knex,
|
||||
SqlEntityManager,
|
||||
} from "@medusajs/framework/mikro-orm/postgresql"
|
||||
import {
|
||||
CalculatedPriceSetDTO,
|
||||
Context,
|
||||
@@ -13,10 +17,6 @@ import {
|
||||
PricingFilters,
|
||||
PricingRepositoryService,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
Knex,
|
||||
SqlEntityManager,
|
||||
} from "@medusajs/framework/mikro-orm/postgresql"
|
||||
|
||||
export class PricingRepository
|
||||
extends MikroOrmBase
|
||||
@@ -172,101 +172,108 @@ export class PricingRepository
|
||||
})
|
||||
|
||||
if (hasComplexContext) {
|
||||
const priceRuleConditions = knex.raw(
|
||||
`
|
||||
(
|
||||
price.rules_count = 0 OR
|
||||
(
|
||||
/* Count all matching rules and compare to total rule count */
|
||||
SELECT COUNT(*)
|
||||
FROM price_rule pr
|
||||
WHERE pr.price_id = price.id
|
||||
AND pr.deleted_at IS NULL
|
||||
AND (
|
||||
${flattenedContext
|
||||
.map(([key, value]) => {
|
||||
if (typeof value === "number") {
|
||||
return `
|
||||
(pr.attribute = ? AND (
|
||||
(pr.operator = 'eq' AND pr.value = ?) OR
|
||||
(pr.operator = 'gt' AND ? > pr.value::numeric) OR
|
||||
(pr.operator = 'gte' AND ? >= pr.value::numeric) OR
|
||||
(pr.operator = 'lt' AND ? < pr.value::numeric) OR
|
||||
(pr.operator = 'lte' AND ? <= pr.value::numeric)
|
||||
))
|
||||
`
|
||||
} else {
|
||||
const normalizeValue = Array.isArray(value) ? value : [value]
|
||||
const placeholders = normalizeValue.map(() => "?").join(",")
|
||||
return `(pr.attribute = ? AND pr.value IN (${placeholders}))`
|
||||
}
|
||||
})
|
||||
.join(" OR ")})
|
||||
) = (
|
||||
/* Get total rule count */
|
||||
SELECT COUNT(*)
|
||||
FROM price_rule pr
|
||||
WHERE pr.price_id = price.id
|
||||
AND pr.deleted_at IS NULL
|
||||
)
|
||||
)
|
||||
`,
|
||||
flattenedContext.flatMap(([key, value]) => {
|
||||
// Build match conditions for LATERAL join
|
||||
const priceRuleMatchConditions = flattenedContext
|
||||
.map(([_key, value]) => {
|
||||
if (typeof value === "number") {
|
||||
return [key, value.toString(), value, value, value, value]
|
||||
return `
|
||||
(pr.attribute = ? AND (
|
||||
(pr.operator = 'eq' AND pr.value = ?) OR
|
||||
(pr.operator = 'gt' AND ? > pr.value::numeric) OR
|
||||
(pr.operator = 'gte' AND ? >= pr.value::numeric) OR
|
||||
(pr.operator = 'lt' AND ? < pr.value::numeric) OR
|
||||
(pr.operator = 'lte' AND ? <= pr.value::numeric)
|
||||
))
|
||||
`
|
||||
} else {
|
||||
const normalizeValue = Array.isArray(value) ? value : [value]
|
||||
return [key, ...normalizeValue]
|
||||
const placeholders = normalizeValue.map(() => "?").join(",")
|
||||
return `(pr.attribute = ? AND pr.value IN (${placeholders}))`
|
||||
}
|
||||
})
|
||||
)
|
||||
.join(" OR ")
|
||||
|
||||
const priceListRuleConditions = knex.raw(
|
||||
`
|
||||
(
|
||||
pl.rules_count = 0 OR
|
||||
(
|
||||
/* Count all matching rules and compare to total rule count */
|
||||
SELECT COUNT(*)
|
||||
FROM price_list_rule plr
|
||||
WHERE plr.price_list_id = pl.id
|
||||
AND plr.deleted_at IS NULL
|
||||
AND (
|
||||
${flattenedContext
|
||||
.map(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((v) => `(plr.attribute = ? AND plr.value @> ?)`)
|
||||
.join(" OR ")
|
||||
}
|
||||
return `(plr.attribute = ? AND plr.value @> ?)`
|
||||
})
|
||||
.join(" OR ")}
|
||||
)
|
||||
) = (
|
||||
/* Get total rule count */
|
||||
SELECT COUNT(*)
|
||||
FROM price_list_rule plr
|
||||
WHERE plr.price_list_id = pl.id
|
||||
AND plr.deleted_at IS NULL
|
||||
)
|
||||
)
|
||||
`,
|
||||
flattenedContext.flatMap(([key, value]) => {
|
||||
const priceRuleMatchParams = flattenedContext.flatMap(([key, value]) => {
|
||||
if (typeof value === "number") {
|
||||
return [key, value.toString(), value, value, value, value]
|
||||
} else {
|
||||
const normalizeValue = Array.isArray(value) ? value : [value]
|
||||
return [key, ...normalizeValue]
|
||||
}
|
||||
})
|
||||
|
||||
const priceListRuleMatchConditions = flattenedContext
|
||||
.map(([_key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((_v) => `(plr.attribute = ? AND plr.value @> ?)`)
|
||||
.join(" OR ")
|
||||
}
|
||||
return `(plr.attribute = ? AND plr.value @> ?)`
|
||||
})
|
||||
.join(" OR ")
|
||||
|
||||
const priceListRuleMatchParams = flattenedContext.flatMap(
|
||||
([key, value]) => {
|
||||
const valueAsArray = Array.isArray(value) ? value : [value]
|
||||
return valueAsArray.flatMap((v) => [key, JSON.stringify(v)])
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Use LATERAL joins to compute matched and total counts in one go
|
||||
query
|
||||
.leftJoin(
|
||||
knex.raw(
|
||||
`LATERAL (
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE ${priceRuleMatchConditions}) as matched_count,
|
||||
COUNT(*) as total_count
|
||||
FROM price_rule pr
|
||||
WHERE pr.price_id = price.id
|
||||
AND pr.deleted_at IS NULL
|
||||
) pr_stats`,
|
||||
priceRuleMatchParams
|
||||
),
|
||||
knex.raw("true")
|
||||
)
|
||||
.leftJoin(
|
||||
knex.raw(
|
||||
`LATERAL (
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE ${priceListRuleMatchConditions}) as matched_count,
|
||||
COUNT(*) as total_count
|
||||
FROM price_list_rule plr
|
||||
WHERE plr.price_list_id = pl.id
|
||||
AND plr.deleted_at IS NULL
|
||||
) plr_stats`,
|
||||
priceListRuleMatchParams
|
||||
),
|
||||
knex.raw("true")
|
||||
)
|
||||
|
||||
query.where((qb) => {
|
||||
qb.whereNull("price.price_list_id")
|
||||
.andWhereRaw(priceRuleConditions)
|
||||
.orWhere((qb2) => {
|
||||
qb2
|
||||
.whereNotNull("price.price_list_id")
|
||||
.whereRaw(priceListRuleConditions)
|
||||
.andWhereRaw(priceRuleConditions)
|
||||
qb.where((qb2) => {
|
||||
// No price list: price rules must match or be zero
|
||||
qb2.whereNull("price.price_list_id").andWhere((qb3) => {
|
||||
qb3
|
||||
.where("price.rules_count", 0)
|
||||
.orWhereRaw("pr_stats.matched_count = price.rules_count")
|
||||
})
|
||||
}).orWhere((qb2) => {
|
||||
// Has price list: both price rules and price list rules must match
|
||||
qb2
|
||||
.whereNotNull("price.price_list_id")
|
||||
.andWhere((qb3) => {
|
||||
qb3
|
||||
.where("price.rules_count", 0)
|
||||
.orWhereRaw("pr_stats.matched_count = price.rules_count")
|
||||
})
|
||||
.andWhere((qb3) => {
|
||||
qb3
|
||||
.where("pl.rules_count", 0)
|
||||
.orWhereRaw("plr_stats.matched_count = pl.rules_count")
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
query.where(function (this: Knex.QueryBuilder) {
|
||||
|
||||
Reference in New Issue
Block a user