chore(medusa): fetch shipping related attributes (#13244)

* chore(medusa): fet shipping related attributes

* some tests

* changeset

* small refactor

* typing

* adapt attribute value

* test

* test again
This commit is contained in:
William Bouchard
2025-08-20 13:03:27 -04:00
committed by GitHub
parent a594d32ba3
commit 6602e893b8
10 changed files with 205 additions and 31 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
chore(medusa): fetch shipping related attributes

View File

@@ -1,10 +1,6 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { Modules, PromotionStatus, PromotionType } from "@medusajs/utils"
import {
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../../helpers/create-admin-user"
import { createAdminUser, generatePublishableKey, generateStoreHeaders, } from "../../../../helpers/create-admin-user"
import { setupTaxStructure } from "../../../../modules/__tests__/fixtures/tax"
import { medusaTshirtProduct } from "../../../__fixtures__/product"
@@ -2408,6 +2404,80 @@ medusaIntegrationTestRunner({
])
)
})
it("return all product target rule attributes by default", async () => {
const response = await api.get(
`/admin/promotions/rule-attribute-options/target-rules`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.attributes).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "product",
value: "items.product.id",
label: "Product",
required: false,
field_type: "multiselect",
operators: expect.anything(),
}),
expect.objectContaining({
id: "product_category",
value: "items.product.categories.id",
label: "Product Category",
required: false,
field_type: "multiselect",
operators: expect.anything(),
}),
expect.objectContaining({
id: "product_collection",
value: "items.product.collection_id",
label: "Product Collection",
required: false,
field_type: "multiselect",
operators: expect.anything(),
}),
expect.objectContaining({
id: "product_type",
value: "items.product.type_id",
label: "Product Type",
required: false,
field_type: "multiselect",
operators: expect.anything(),
}),
expect.objectContaining({
id: "product_tag",
value: "items.product.tags.id",
label: "Product Tag",
required: false,
field_type: "multiselect",
operators: expect.anything(),
}),
])
)
})
it("return all target rule attributes when application method target type is shipping_methods", async () => {
const response = await api.get(
`/admin/promotions/rule-attribute-options/target-rules?application_method_target_type=shipping_methods`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.attributes).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "shipping_option_type",
value: "shipping_methods.shipping_option.shipping_option_type_id",
label: "Shipping Option Type",
required: false,
field_type: "multiselect",
operators: expect.anything(),
}),
])
)
})
})
describe("GET /admin/promotions/rule-value-options/:ruleType/:ruleAttributeId", () => {
@@ -2677,6 +2747,36 @@ medusaIntegrationTestRunner({
{ label: "test tag 2", value: tag2.id },
])
)
const soType1 = (
await api.post(
"/admin/shipping-option-types",
{ label: "Test 1", code: "test_1" },
adminHeaders
)
).data.shipping_option_type
const soType2 = (
await api.post(
"/admin/shipping-option-types",
{ label: "Test 2", code: "test_2" },
adminHeaders
)
).data.shipping_option_type
response = await api.get(
`/admin/promotions/rule-value-options/target-rules/shipping_option_type?application_method_target_type=shipping_methods`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.values.length).toEqual(2)
expect(response.data.values).toEqual(
expect.arrayContaining([
{ label: "Test 1", value: soType1.id },
{ label: "Test 2", value: soType2.id },
])
)
})
})
})

View File

@@ -1,5 +1,8 @@
import { BaseFilterable, OperatorMap } from "../../../dal"
import { ApplicationMethodTypeValues } from "../../../promotion"
import {
ApplicationMethodTypeValues,
PromotionTypeValues,
} from "../../../promotion"
import { FindParams, SelectParams } from "../../common"
export interface AdminGetPromotionParams extends SelectParams {}
@@ -59,13 +62,13 @@ export interface AdminGetPromotionsParams
}
export interface AdminGetPromotionRuleParams {
promotion_type?: string
application_method_type?: string
promotion_type?: PromotionTypeValues
application_method_type?: ApplicationMethodTypeValues
}
export interface AdminGetPromotionRuleTypeParams extends SelectParams {
promotion_type?: string
application_method_type?: string
promotion_type?: PromotionTypeValues
application_method_type?: ApplicationMethodTypeValues
}
export interface AdminGetPromotionsRuleValueParams extends FindParams {

View File

@@ -37,6 +37,9 @@ export const GET = async (
promotionType: promotion?.type || req.query.promotion_type,
applicationMethodType:
promotion?.application_method?.type || req.query.application_method_type,
applicationMethodTargetType:
promotion?.application_method?.target_type ||
req.query.application_method_target_type,
})[ruleType]
const promotionRules: any[] = []

View File

@@ -4,6 +4,11 @@ import {
MedusaResponse,
} from "@medusajs/framework/http"
import { getRuleAttributesMap, validateRuleType } from "../../utils"
import {
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
PromotionTypeValues,
} from "@medusajs/types"
export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminGetPromotionRuleParams>,
@@ -15,8 +20,11 @@ export const GET = async (
const attributes =
getRuleAttributesMap({
promotionType: req.query.promotion_type as string,
applicationMethodType: req.query.application_method_type as string,
promotionType: req.query.promotion_type as PromotionTypeValues,
applicationMethodType: req.query
.application_method_type as ApplicationMethodTypeValues,
applicationMethodTargetType: req.query
.application_method_target_type as ApplicationMethodTargetTypeValues,
})[ruleType] || []
res.json({

View File

@@ -13,6 +13,10 @@ import {
validateRuleType,
} from "../../../utils"
import { AdminGetPromotionRuleParamsType } from "../../../validators"
import {
ApplicationMethodTargetTypeValues,
RuleTypeValues,
} from "@medusajs/types"
/*
This endpoint returns all the potential values for rules (promotion rules, target rules and buy rules)
@@ -25,12 +29,7 @@ export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetPromotionRuleParamsType>,
res: MedusaResponse<HttpTypes.AdminRuleValueOptionsListResponse>
) => {
const {
rule_type: ruleType,
rule_attribute_id: ruleAttributeId,
promotion_type: promotionType,
application_method_type: applicationMethodType,
} = req.params
const { rule_type: ruleType, rule_attribute_id: ruleAttributeId } = req.params
const queryConfig = ruleQueryConfigurations[ruleAttributeId]
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const filterableFields = req.filterableFields
@@ -43,12 +42,20 @@ export const GET = async (
validateRuleType(ruleType)
validateRuleAttribute({
promotionType,
ruleType,
ruleType: ruleType as RuleTypeValues,
ruleAttributeId,
applicationMethodType,
promotionType: undefined,
applicationMethodType: undefined,
applicationMethodTargetType:
filterableFields.application_method_target_type as
| ApplicationMethodTargetTypeValues
| undefined,
})
if (filterableFields.application_method_target_type) {
delete filterableFields.application_method_target_type
}
const { rows, metadata } = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: queryConfig.entryPoint,

View File

@@ -1,9 +1,15 @@
import {
ApplicationMethodTargetType,
ApplicationMethodType,
PromotionType,
RuleOperator,
} from "@medusajs/framework/utils"
import { operatorsMap } from "./operators-map"
import {
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
PromotionTypeValues,
} from "@medusajs/types"
export enum DisguisedRule {
APPLY_TO_QUANTITY = "apply_to_quantity",
@@ -46,7 +52,7 @@ const ruleAttributes = [
},
]
const commonAttributes = [
const itemsAttributes = [
{
id: "product",
value: "items.product.id",
@@ -89,6 +95,17 @@ const commonAttributes = [
},
]
const shippingMethodsAttributes = [
{
id: "shipping_option_type",
value: "shipping_methods.shipping_option.shipping_option_type_id",
label: "Shipping Option Type",
required: false,
field_type: "multiselect",
operators: Object.values(operatorsMap),
},
]
const currencyRule = {
id: DisguisedRule.CURRENCY_CODE,
value: DisguisedRule.CURRENCY_CODE,
@@ -127,14 +144,24 @@ const buyGetTargetRules = [
export const getRuleAttributesMap = ({
promotionType,
applicationMethodType,
applicationMethodTargetType,
}: {
promotionType?: string
applicationMethodType?: string
promotionType?: PromotionTypeValues
applicationMethodType?: ApplicationMethodTypeValues
applicationMethodTargetType?: ApplicationMethodTargetTypeValues
}) => {
const map = {
rules: [...ruleAttributes],
"target-rules": [...commonAttributes],
"buy-rules": [...commonAttributes],
"target-rules":
applicationMethodTargetType ===
ApplicationMethodTargetType.SHIPPING_METHODS
? [...shippingMethodsAttributes]
: [...itemsAttributes],
"buy-rules":
applicationMethodTargetType ===
ApplicationMethodTargetType.SHIPPING_METHODS
? [...shippingMethodsAttributes]
: [...itemsAttributes],
}
if (applicationMethodType === ApplicationMethodType.FIXED) {

View File

@@ -49,4 +49,9 @@ export const ruleQueryConfigurations = {
labelAttr: "value",
valueAttr: "id",
},
shipping_option_type: {
entryPoint: "shipping_option_type",
labelAttr: "label",
valueAttr: "id",
},
}

View File

@@ -1,19 +1,32 @@
import { MedusaError } from "@medusajs/framework/utils"
import { getRuleAttributesMap } from "./rule-attributes-map"
import {
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
PromotionTypeValues,
RuleTypeValues,
} from "@medusajs/types"
export function validateRuleAttribute(attributes: {
promotionType: string | undefined
ruleType: string
promotionType: PromotionTypeValues | undefined
ruleType: RuleTypeValues
ruleAttributeId: string
applicationMethodType?: string
applicationMethodType?: ApplicationMethodTypeValues
applicationMethodTargetType?: ApplicationMethodTargetTypeValues
}) {
const { promotionType, ruleType, ruleAttributeId, applicationMethodType } =
attributes
const {
promotionType,
ruleType,
ruleAttributeId,
applicationMethodType,
applicationMethodTargetType,
} = attributes
const ruleAttributes =
getRuleAttributesMap({
promotionType,
applicationMethodType,
applicationMethodTargetType,
})[ruleType] || []
const ruleAttribute = ruleAttributes.find((obj) => obj.id === ruleAttributeId)

View File

@@ -54,6 +54,7 @@ export type AdminGetPromotionRuleParamsType = z.infer<
export const AdminGetPromotionRuleParams = z.object({
promotion_type: z.string().optional(),
application_method_type: z.string().optional(),
application_method_target_type: z.string().optional(),
})
export type AdminGetPromotionRuleTypeParamsType = z.infer<
@@ -63,6 +64,7 @@ export const AdminGetPromotionRuleTypeParams = createSelectParams().merge(
z.object({
promotion_type: z.string().optional(),
application_method_type: z.string().optional(),
application_method_target_type: z.string().optional(),
})
)
@@ -76,6 +78,7 @@ export const AdminGetPromotionsRuleValueParams = createFindParams({
z.object({
q: z.string().optional(),
value: z.union([z.string(), z.array(z.string())]).optional(),
application_method_target_type: z.string().optional(),
})
)