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
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
chore(medusa): fetch shipping related attributes
@@ -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 },
])
)
}) })
}) })
}) })
@@ -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 {
@@ -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[] = []
@@ -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({
@@ -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,
@@ -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) {
@@ -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",
},
} }
@@ -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)
@@ -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(),
}) })
) )