feat(medusa,pricing): Cart pricing context with customer group (#10579)

* fix(carts): Fixes cart modifications not accounting for certain price lists (#10493)

*What*

* Fixes #10490
* Expands any available customer_id into its customer_group_ids for cart
  updates that add line items.

*Why*

* Cart updates from the storefront were overriding any valid price lists
  that were correctly being shown in the storefront's product pages.

*How*

* Adds a new workflow step that expands an optional customer_id into the
  customer_group_ids it belongs to.
* Uses this step in the addToCartWorkflow and
  updateLineItemInCartWorkflow workflows.

*Testing*
* Using medusa-dev to test on a local backend.
* Adds integration tests for the addToCart and updateLineItemInCart
  workflows.

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>

* chore: update cart workflows to accept new pricing context

* chore: add transfer specs

* chore: fix specs

* chore: modify types + specs

* chore: add data migration + dashboard changes

* chore: fix update line item workflow

* chore: add changeset + unskip spec

---------

Co-authored-by: Sergio Campamá <sergiocampama@gmail.com>
This commit is contained in:
Riqwan Thamir
2024-12-17 11:10:30 +01:00
committed by GitHub
co-authored by Sergio Campamá
parent 0c49470066
commit 6367bccde8
29 changed files with 1090 additions and 166 deletions
@@ -0,0 +1,11 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20241212190401 extends Migration {
async up(): Promise<void> {
this.addSql(
`UPDATE price_list_rule SET attribute = 'customer.groups.id' WHERE attribute = 'customer_group_id';`
)
}
async down(): Promise<void> {}
}
@@ -1,4 +1,6 @@
import {
flattenObjectToKeyValuePairs,
isPresent,
MedusaError,
MikroOrmBase,
PriceListStatus,
@@ -58,6 +60,14 @@ export class PricingRepository
return []
}
const flattenedContext = Object.entries(
flattenObjectToKeyValuePairs(context)
).filter(
([key, value]) =>
(isPresent(value) && !Array.isArray(value)) ||
(Array.isArray(value) && value.flat(1).length)
)
// Gets all the prices where rules match for each of the contexts
// that the price set is configured for
const priceSubQueryKnex = knex({
@@ -135,19 +145,20 @@ export class PricingRepository
priceBuilder
.whereNull("price.price_list_id")
.andWhere((withoutPriceListBuilder) => {
for (const [key, value] of Object.entries(context)) {
for (const [key, value] of flattenedContext) {
withoutPriceListBuilder.orWhere((orBuilder) => {
orBuilder.where("pr.attribute", key)
if (typeof value === "number") {
orBuilder.where((operatorGroupBuilder) => {
buildOperatorQueries(operatorGroupBuilder, value)
})
buildOperatorQueries(orBuilder, value)
} else {
orBuilder.where({ "pr.value": value })
const normalizeValue = Array.isArray(value) ? value : [value]
orBuilder.whereIn("pr.value", normalizeValue)
}
})
}
withoutPriceListBuilder.orWhere("price.rules_count", "=", 0)
})
})
@@ -171,7 +182,7 @@ export class PricingRepository
})
.andWhere(function () {
this.andWhere(function () {
for (const [key, value] of Object.entries(context)) {
for (const [key, value] of flattenedContext) {
this.orWhere({ "plr.attribute": key })
this.where(
"plr.value",
@@ -185,14 +196,18 @@ export class PricingRepository
this.andWhere(function () {
this.andWhere((contextBuilder) => {
for (const [key, value] of Object.entries(context)) {
for (const [key, value] of flattenedContext) {
contextBuilder.orWhere((orBuilder) => {
orBuilder.where("pr.attribute", key)
if (typeof value === "number") {
buildOperatorQueries(orBuilder, value)
} else {
orBuilder.where({ "pr.value": value })
const normalizeValue = Array.isArray(value)
? value
: [value]
orBuilder.whereIn("pr.value", normalizeValue)
}
})
}