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 { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { Modules, PromotionStatus, PromotionType } from "@medusajs/utils" import { Modules, PromotionStatus, PromotionType } from "@medusajs/utils"
import { import { createAdminUser, generatePublishableKey, generateStoreHeaders, } from "../../../../helpers/create-admin-user"
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../../helpers/create-admin-user"
import { setupTaxStructure } from "../../../../modules/__tests__/fixtures/tax" import { setupTaxStructure } from "../../../../modules/__tests__/fixtures/tax"
import { medusaTshirtProduct } from "../../../__fixtures__/product" 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", () => { describe("GET /admin/promotions/rule-value-options/:ruleType/:ruleAttributeId", () => {
@@ -2677,6 +2747,36 @@ medusaIntegrationTestRunner({
{ label: "test tag 2", value: tag2.id }, { 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 { BaseFilterable, OperatorMap } from "../../../dal"
import { ApplicationMethodTypeValues } from "../../../promotion" import {
ApplicationMethodTypeValues,
PromotionTypeValues,
} from "../../../promotion"
import { FindParams, SelectParams } from "../../common" import { FindParams, SelectParams } from "../../common"
export interface AdminGetPromotionParams extends SelectParams {} export interface AdminGetPromotionParams extends SelectParams {}
@@ -59,13 +62,13 @@ export interface AdminGetPromotionsParams
} }
export interface AdminGetPromotionRuleParams { export interface AdminGetPromotionRuleParams {
promotion_type?: string promotion_type?: PromotionTypeValues
application_method_type?: string application_method_type?: ApplicationMethodTypeValues
} }
export interface AdminGetPromotionRuleTypeParams extends SelectParams { export interface AdminGetPromotionRuleTypeParams extends SelectParams {
promotion_type?: string promotion_type?: PromotionTypeValues
application_method_type?: string application_method_type?: ApplicationMethodTypeValues
} }
export interface AdminGetPromotionsRuleValueParams extends FindParams { export interface AdminGetPromotionsRuleValueParams extends FindParams {

View File

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

View File

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

View File

@@ -13,6 +13,10 @@ import {
validateRuleType, validateRuleType,
} from "../../../utils" } from "../../../utils"
import { AdminGetPromotionRuleParamsType } from "../../../validators" 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) 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>, req: AuthenticatedMedusaRequest<AdminGetPromotionRuleParamsType>,
res: MedusaResponse<HttpTypes.AdminRuleValueOptionsListResponse> res: MedusaResponse<HttpTypes.AdminRuleValueOptionsListResponse>
) => { ) => {
const { const { rule_type: ruleType, rule_attribute_id: ruleAttributeId } = req.params
rule_type: ruleType,
rule_attribute_id: ruleAttributeId,
promotion_type: promotionType,
application_method_type: applicationMethodType,
} = req.params
const queryConfig = ruleQueryConfigurations[ruleAttributeId] const queryConfig = ruleQueryConfigurations[ruleAttributeId]
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const filterableFields = req.filterableFields const filterableFields = req.filterableFields
@@ -43,12 +42,20 @@ export const GET = async (
validateRuleType(ruleType) validateRuleType(ruleType)
validateRuleAttribute({ validateRuleAttribute({
promotionType, ruleType: ruleType as RuleTypeValues,
ruleType,
ruleAttributeId, 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( const { rows, metadata } = await remoteQuery(
remoteQueryObjectFromString({ remoteQueryObjectFromString({
entryPoint: queryConfig.entryPoint, entryPoint: queryConfig.entryPoint,

View File

@@ -1,9 +1,15 @@
import { import {
ApplicationMethodTargetType,
ApplicationMethodType, ApplicationMethodType,
PromotionType, PromotionType,
RuleOperator, RuleOperator,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { operatorsMap } from "./operators-map" import { operatorsMap } from "./operators-map"
import {
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
PromotionTypeValues,
} from "@medusajs/types"
export enum DisguisedRule { export enum DisguisedRule {
APPLY_TO_QUANTITY = "apply_to_quantity", APPLY_TO_QUANTITY = "apply_to_quantity",
@@ -46,7 +52,7 @@ const ruleAttributes = [
}, },
] ]
const commonAttributes = [ const itemsAttributes = [
{ {
id: "product", id: "product",
value: "items.product.id", 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 = { const currencyRule = {
id: DisguisedRule.CURRENCY_CODE, id: DisguisedRule.CURRENCY_CODE,
value: DisguisedRule.CURRENCY_CODE, value: DisguisedRule.CURRENCY_CODE,
@@ -127,14 +144,24 @@ const buyGetTargetRules = [
export const getRuleAttributesMap = ({ export const getRuleAttributesMap = ({
promotionType, promotionType,
applicationMethodType, applicationMethodType,
applicationMethodTargetType,
}: { }: {
promotionType?: string promotionType?: PromotionTypeValues
applicationMethodType?: string applicationMethodType?: ApplicationMethodTypeValues
applicationMethodTargetType?: ApplicationMethodTargetTypeValues
}) => { }) => {
const map = { const map = {
rules: [...ruleAttributes], rules: [...ruleAttributes],
"target-rules": [...commonAttributes], "target-rules":
"buy-rules": [...commonAttributes], applicationMethodTargetType ===
ApplicationMethodTargetType.SHIPPING_METHODS
? [...shippingMethodsAttributes]
: [...itemsAttributes],
"buy-rules":
applicationMethodTargetType ===
ApplicationMethodTargetType.SHIPPING_METHODS
? [...shippingMethodsAttributes]
: [...itemsAttributes],
} }
if (applicationMethodType === ApplicationMethodType.FIXED) { if (applicationMethodType === ApplicationMethodType.FIXED) {

View File

@@ -49,4 +49,9 @@ export const ruleQueryConfigurations = {
labelAttr: "value", labelAttr: "value",
valueAttr: "id", 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 { MedusaError } from "@medusajs/framework/utils"
import { getRuleAttributesMap } from "./rule-attributes-map" import { getRuleAttributesMap } from "./rule-attributes-map"
import {
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
PromotionTypeValues,
RuleTypeValues,
} from "@medusajs/types"
export function validateRuleAttribute(attributes: { export function validateRuleAttribute(attributes: {
promotionType: string | undefined promotionType: PromotionTypeValues | undefined
ruleType: string ruleType: RuleTypeValues
ruleAttributeId: string ruleAttributeId: string
applicationMethodType?: string applicationMethodType?: ApplicationMethodTypeValues
applicationMethodTargetType?: ApplicationMethodTargetTypeValues
}) { }) {
const { promotionType, ruleType, ruleAttributeId, applicationMethodType } = const {
attributes promotionType,
ruleType,
ruleAttributeId,
applicationMethodType,
applicationMethodTargetType,
} = attributes
const ruleAttributes = const ruleAttributes =
getRuleAttributesMap({ getRuleAttributesMap({
promotionType, promotionType,
applicationMethodType, applicationMethodType,
applicationMethodTargetType,
})[ruleType] || [] })[ruleType] || []
const ruleAttribute = ruleAttributes.find((obj) => obj.id === ruleAttributeId) const ruleAttribute = ruleAttributes.find((obj) => obj.id === ruleAttributeId)

View File

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