feat(modules-sdk): remote query context filter (#7153)
What: - Remote query now handles `context` keywork in the arguments to forward it as a filter to the method `list` - Pricing module `list` method returning `calculated_price` if requested as a field
This commit is contained in:
committed by
GitHub
parent
aef222278b
commit
4b57c5d286
10
.changeset/six-rats-pretend.md
Normal file
10
.changeset/six-rats-pretend.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"@medusajs/link-modules": patch
|
||||
"@medusajs/modules-sdk": patch
|
||||
"@medusajs/core-flows": patch
|
||||
"@medusajs/pricing": patch
|
||||
"@medusajs/types": patch
|
||||
"@medusajs/utils": patch
|
||||
---
|
||||
|
||||
Remote query supporting context
|
||||
@@ -71,6 +71,10 @@ medusaIntegrationTestRunner({
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
},
|
||||
{
|
||||
amount: 5000,
|
||||
currency_code: "eur",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -91,6 +95,17 @@ medusaIntegrationTestRunner({
|
||||
price_set_link: {
|
||||
fields: ["id", "price_set_id", "shipping_option_id"],
|
||||
},
|
||||
prices: {
|
||||
fields: ["amount", "currency_code"],
|
||||
},
|
||||
calculated_price: {
|
||||
fields: ["calculated_amount", "currency_code"],
|
||||
__args: {
|
||||
context: {
|
||||
currency_code: "eur",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -103,6 +118,20 @@ medusaIntegrationTestRunner({
|
||||
price_set_id: priceSet.id,
|
||||
shipping_option_id: shippingOption.id,
|
||||
}),
|
||||
prices: [
|
||||
expect.objectContaining({
|
||||
amount: 5000,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
],
|
||||
calculated_price: expect.objectContaining({
|
||||
calculated_amount: 5000,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
|
||||
import { addShippingMethodToCartStep } from "../steps"
|
||||
import { getShippingOptionPriceSetsStep } from "../steps/get-shipping-option-price-sets"
|
||||
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
|
||||
import { updateTaxLinesStep } from "../steps/update-tax-lines"
|
||||
|
||||
@@ -28,32 +27,30 @@ export const addShippingMethodToWorkflow = createWorkflow(
|
||||
return (data.input.options ?? []).map((i) => i.id)
|
||||
})
|
||||
|
||||
const priceSets = getShippingOptionPriceSetsStep({
|
||||
optionIds: optionIds,
|
||||
context: { currency_code: input.currency_code },
|
||||
})
|
||||
|
||||
const shippingOptions = useRemoteQueryStep({
|
||||
entry_point: "shipping_option",
|
||||
fields: ["id", "name"],
|
||||
fields: ["id", "name", "calculated_price.calculated_amount"],
|
||||
variables: {
|
||||
id: optionIds,
|
||||
calculated_price: {
|
||||
context: {
|
||||
currency_code: input.currency_code,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const shippingMethodInput = transform(
|
||||
{ priceSets, input, shippingOptions },
|
||||
{ input, shippingOptions },
|
||||
(data) => {
|
||||
const options = (data.input.options ?? []).map((option) => {
|
||||
const shippingOption = data.shippingOptions.find(
|
||||
(so) => so.id === option.id
|
||||
)!
|
||||
|
||||
const price = data.priceSets[option.id].calculated_amount
|
||||
|
||||
return {
|
||||
shipping_option_id: shippingOption.id,
|
||||
amount: price,
|
||||
amount: shippingOption.calculated_price.calculated_amount,
|
||||
data: option.data ?? {},
|
||||
name: shippingOption.name,
|
||||
cart_id: data.input.cart_id,
|
||||
|
||||
@@ -43,6 +43,10 @@ export const ProductVariantPriceSet: ModuleJoinerConfig = {
|
||||
serviceName: Modules.PRODUCT,
|
||||
fieldAlias: {
|
||||
price_set: "price_set_link.price_set",
|
||||
calculated_price: {
|
||||
path: "price_set_link.price_set.calculated_price",
|
||||
forwardArgumentsOnPath: ["price_set_link.price_set"],
|
||||
},
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.ProductVariantPriceSet,
|
||||
|
||||
@@ -44,6 +44,10 @@ export const ShippingOptionPriceSet: ModuleJoinerConfig = {
|
||||
path: "price_set_link.price_set.prices",
|
||||
isList: true,
|
||||
},
|
||||
calculated_price: {
|
||||
path: "price_set_link.price_set.calculated_price",
|
||||
forwardArgumentsOnPath: ["price_set_link.price_set"],
|
||||
},
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.ShippingOptionPriceSet,
|
||||
|
||||
@@ -191,7 +191,9 @@ export class RemoteQuery {
|
||||
|
||||
for (const arg of expand.args || []) {
|
||||
if (arg.name === "filters" && arg.value) {
|
||||
filters = { ...arg.value }
|
||||
filters = { ...filters, ...arg.value }
|
||||
} else if (arg.name === "context" && arg.value) {
|
||||
filters["context"] = arg.value
|
||||
} else if (availableOptions.includes(arg.name)) {
|
||||
const argName = availableOptionsAlias.has(arg.name)
|
||||
? availableOptionsAlias.get(arg.name)!
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
CreatePricesDTO,
|
||||
CreatePriceSetDTO,
|
||||
DAL,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
@@ -152,6 +153,95 @@ export default class PricingModuleService<
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
private setupCalculatedPriceConfig_(
|
||||
filters,
|
||||
config
|
||||
): PricingContext["context"] | undefined {
|
||||
const fieldIdx = config.relations?.indexOf("calculated_price")
|
||||
const shouldCalculatePrice = fieldIdx > -1
|
||||
if (!shouldCalculatePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
let pricingContext = filters.context ?? {}
|
||||
|
||||
// cleanup virtual field "calculated_price"
|
||||
config.relations?.splice(fieldIdx, 1)
|
||||
delete filters.context
|
||||
|
||||
return pricingContext
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async list(
|
||||
filters: PricingTypes.FilterablePriceSetProps = {},
|
||||
config: FindConfig<PricingTypes.PriceSetDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<PriceSetDTO[]> {
|
||||
const pricingContext = this.setupCalculatedPriceConfig_(filters, config)
|
||||
|
||||
const priceSets = await super.list(filters, config, sharedContext)
|
||||
|
||||
if (pricingContext && priceSets.length) {
|
||||
const priceSetIds: string[] = []
|
||||
const priceSetMap = new Map()
|
||||
for (const priceSet of priceSets) {
|
||||
priceSetIds.push(priceSet.id)
|
||||
priceSetMap.set(priceSet.id, priceSet)
|
||||
}
|
||||
|
||||
const calculatedPrices = await this.calculatePrices(
|
||||
{ id: priceSets.map((p) => p.id) },
|
||||
{ context: pricingContext },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
for (const calculatedPrice of calculatedPrices) {
|
||||
const priceSet = priceSetMap.get(calculatedPrice.id)
|
||||
priceSet.calculated_price = calculatedPrice
|
||||
}
|
||||
}
|
||||
|
||||
return priceSets
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCount(
|
||||
filters: PricingTypes.FilterablePriceSetProps = {},
|
||||
config: FindConfig<PricingTypes.PriceSetDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[PriceSetDTO[], number]> {
|
||||
const pricingContext = this.setupCalculatedPriceConfig_(filters, config)
|
||||
|
||||
const [priceSets, count] = await super.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (pricingContext && priceSets.length) {
|
||||
const priceSetIds: string[] = []
|
||||
const priceSetMap = new Map()
|
||||
for (const priceSet of priceSets) {
|
||||
priceSetIds.push(priceSet.id)
|
||||
priceSetMap.set(priceSet.id, priceSet)
|
||||
}
|
||||
|
||||
const calculatedPrices = await this.calculatePrices(
|
||||
{ id: priceSets.map((p) => p.id) },
|
||||
{ context: pricingContext },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
for (const calculatedPrice of calculatedPrices) {
|
||||
const priceSet = priceSetMap.get(calculatedPrice.id)
|
||||
priceSet.calculated_price = calculatedPrice
|
||||
}
|
||||
}
|
||||
|
||||
return [priceSets, count]
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async calculatePrices(
|
||||
pricingFilters: PricingFilters,
|
||||
@@ -171,7 +261,9 @@ export default class PricingModuleService<
|
||||
(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 rules_count first for exact match and then deafult_priority of the rule_type
|
||||
// inject custom price selection here
|
||||
|
||||
// TODO: inject custom price selection here
|
||||
|
||||
const prices = pricesSetPricesMap.get(priceSetId) || []
|
||||
const priceListPrice = prices.find((p) => p.price_list_id)
|
||||
|
||||
@@ -749,7 +841,7 @@ export default class PricingModuleService<
|
||||
|
||||
return {
|
||||
...rest,
|
||||
title: "test", // TODO: accept title
|
||||
title: "", // TODO: accept title
|
||||
rules_count: numberOfRules,
|
||||
price_rules: Array.from(rulesDataMap.values()),
|
||||
}
|
||||
|
||||
@@ -58,6 +58,12 @@ export interface PriceSetDTO {
|
||||
* The prices that belong to this price set.
|
||||
*/
|
||||
prices?: MoneyAmountDTO[]
|
||||
|
||||
/**
|
||||
* The calculated price based on the context.
|
||||
*/
|
||||
calculated_price?: CalculatedPriceSet
|
||||
|
||||
/**
|
||||
* The rule types applied on this price set.
|
||||
*/
|
||||
@@ -321,7 +327,8 @@ export interface UpdatePriceSetDTO {
|
||||
* Filters to apply on price sets.
|
||||
*/
|
||||
export interface FilterablePriceSetProps
|
||||
extends BaseFilterable<FilterablePriceSetProps> {
|
||||
extends BaseFilterable<FilterablePriceSetProps>,
|
||||
PricingContext {
|
||||
/**
|
||||
* IDs to filter price sets by.
|
||||
*/
|
||||
|
||||
@@ -33,13 +33,23 @@ describe("remoteQueryObjectFromString", function () {
|
||||
it("should return a remote query object", function () {
|
||||
const output = remoteQueryObjectFromString({
|
||||
entryPoint: "product",
|
||||
variables: {},
|
||||
variables: {
|
||||
q: "name",
|
||||
options: {
|
||||
name: "option_name",
|
||||
},
|
||||
"options.values": {
|
||||
value: 123,
|
||||
},
|
||||
},
|
||||
fields,
|
||||
})
|
||||
|
||||
expect(output).toEqual({
|
||||
product: {
|
||||
__args: {},
|
||||
__args: {
|
||||
q: "name",
|
||||
},
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
@@ -54,6 +64,9 @@ describe("remoteQueryObjectFromString", function () {
|
||||
},
|
||||
|
||||
options: {
|
||||
__args: {
|
||||
name: "option_name",
|
||||
},
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
@@ -64,6 +77,9 @@ describe("remoteQueryObjectFromString", function () {
|
||||
"metadata",
|
||||
],
|
||||
values: {
|
||||
__args: {
|
||||
value: 123,
|
||||
},
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isObject } from "./is-object"
|
||||
|
||||
/**
|
||||
* Convert a string fields array to a remote query object
|
||||
* @param config - The configuration object
|
||||
@@ -109,9 +111,7 @@ export function remoteQueryObjectFromString(
|
||||
},
|
||||
}
|
||||
|
||||
if (variables) {
|
||||
remoteJoinerConfig[entryKey]["__args"] = variables
|
||||
}
|
||||
const usedVariables = new Set()
|
||||
|
||||
for (const field of fields) {
|
||||
if (!field.includes(".")) {
|
||||
@@ -122,8 +122,19 @@ export function remoteQueryObjectFromString(
|
||||
const fieldSegments = field.split(".")
|
||||
const fieldProperty = fieldSegments.pop()
|
||||
|
||||
let combinedPath = ""
|
||||
|
||||
const deepConfigRef = fieldSegments.reduce((acc, curr) => {
|
||||
acc[curr] ??= {}
|
||||
combinedPath = combinedPath ? combinedPath + "." + curr : curr
|
||||
|
||||
if (isObject(variables) && combinedPath in variables) {
|
||||
acc[curr] ??= {}
|
||||
acc[curr]["__args"] = variables[combinedPath]
|
||||
usedVariables.add(combinedPath)
|
||||
} else {
|
||||
acc[curr] ??= {}
|
||||
}
|
||||
|
||||
return acc[curr]
|
||||
}, remoteJoinerConfig[entryKey])
|
||||
|
||||
@@ -131,5 +142,14 @@ export function remoteQueryObjectFromString(
|
||||
deepConfigRef["fields"].push(fieldProperty)
|
||||
}
|
||||
|
||||
const topLevelArgs = {}
|
||||
for (const key of Object.keys(variables ?? {})) {
|
||||
if (!usedVariables.has(key)) {
|
||||
topLevelArgs[key] = variables[key]
|
||||
}
|
||||
}
|
||||
|
||||
remoteJoinerConfig[entryKey]["__args"] = topLevelArgs ?? {}
|
||||
|
||||
return remoteJoinerConfig
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user