chore: buyget templates add default target + buy rules (#7500)
* chore: buyget templates add default target + buy rules * chore: reposition * chore: address comments * chore: added fixes * chore: fix typo * chore: fix strictness checks
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { AdminGetPromotionsParams } from "@medusajs/medusa"
|
||||
import { AdminRuleValueOptionsListResponse } from "@medusajs/types"
|
||||
import {
|
||||
AdminPromotionRuleListResponse,
|
||||
AdminRuleAttributeOptionsListResponse,
|
||||
AdminRuleOperatorOptionsListResponse,
|
||||
AdminRuleValueOptionsListResponse,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
useMutation,
|
||||
@@ -21,21 +26,23 @@ import {
|
||||
PromotionDeleteRes,
|
||||
PromotionListRes,
|
||||
PromotionRes,
|
||||
PromotionRuleAttributesListRes,
|
||||
PromotionRuleOperatorsListRes,
|
||||
PromotionRulesListRes,
|
||||
} from "../../types/api-responses"
|
||||
import { campaignsQueryKeys } from "./campaigns"
|
||||
|
||||
const PROMOTIONS_QUERY_KEY = "promotions" as const
|
||||
export const promotionsQueryKeys = {
|
||||
...queryKeysFactory(PROMOTIONS_QUERY_KEY),
|
||||
listRules: (id: string | null, ruleType: string) => [
|
||||
// TODO: handle invalidations properly
|
||||
listRules: (
|
||||
id: string | null,
|
||||
ruleType: string,
|
||||
query?: Record<string, string>
|
||||
) => [PROMOTIONS_QUERY_KEY, id, ruleType, query],
|
||||
listRuleAttributes: (ruleType: string, promotionType?: string) => [
|
||||
PROMOTIONS_QUERY_KEY,
|
||||
id,
|
||||
ruleType,
|
||||
promotionType,
|
||||
],
|
||||
listRuleAttributes: (ruleType: string) => [PROMOTIONS_QUERY_KEY, ruleType],
|
||||
listRuleValues: (ruleType: string, ruleValue: string, query: object) => [
|
||||
PROMOTIONS_QUERY_KEY,
|
||||
ruleType,
|
||||
@@ -64,19 +71,20 @@ export const usePromotion = (
|
||||
export const usePromotionRules = (
|
||||
id: string | null,
|
||||
ruleType: string,
|
||||
query?: Record<string, string>,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
PromotionRulesListRes,
|
||||
AdminPromotionRuleListResponse,
|
||||
Error,
|
||||
PromotionRulesListRes,
|
||||
AdminPromotionRuleListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: promotionsQueryKeys.listRules(id, ruleType),
|
||||
queryFn: async () => client.promotions.listRules(id, ruleType),
|
||||
queryKey: promotionsQueryKeys.listRules(id, ruleType, query),
|
||||
queryFn: async () => client.promotions.listRules(id, ruleType, query),
|
||||
...options,
|
||||
})
|
||||
|
||||
@@ -102,9 +110,9 @@ export const usePromotions = (
|
||||
export const usePromotionRuleOperators = (
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
PromotionListRes,
|
||||
AdminRuleOperatorOptionsListResponse,
|
||||
Error,
|
||||
PromotionRuleOperatorsListRes,
|
||||
AdminRuleOperatorOptionsListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
@@ -121,19 +129,21 @@ export const usePromotionRuleOperators = (
|
||||
|
||||
export const usePromotionRuleAttributes = (
|
||||
ruleType: string,
|
||||
promotionType?: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
PromotionListRes,
|
||||
AdminRuleAttributeOptionsListResponse,
|
||||
Error,
|
||||
PromotionRuleAttributesListRes,
|
||||
AdminRuleAttributeOptionsListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: promotionsQueryKeys.listRuleAttributes(ruleType),
|
||||
queryFn: async () => client.promotions.listRuleAttributes(ruleType),
|
||||
queryKey: promotionsQueryKeys.listRuleAttributes(ruleType, promotionType),
|
||||
queryFn: async () =>
|
||||
client.promotions.listRuleAttributes(ruleType, promotionType),
|
||||
...options,
|
||||
})
|
||||
|
||||
@@ -207,10 +217,7 @@ export const useUpdatePromotion = (
|
||||
return useMutation({
|
||||
mutationFn: (payload) => client.promotions.update(id, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: promotionsQueryKeys.detail(id),
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.all })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
@@ -226,10 +233,7 @@ export const usePromotionAddRules = (
|
||||
return useMutation({
|
||||
mutationFn: (payload) => client.promotions.addRules(id, ruleType, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: promotionsQueryKeys.detail(id),
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.all })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
@@ -250,10 +254,7 @@ export const usePromotionRemoveRules = (
|
||||
mutationFn: (payload) =>
|
||||
client.promotions.removeRules(id, ruleType, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: promotionsQueryKeys.detail(id),
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.all })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
@@ -274,13 +275,7 @@ export const usePromotionUpdateRules = (
|
||||
mutationFn: (payload) =>
|
||||
client.promotions.updateRules(id, ruleType, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: promotionsQueryKeys.listRules(id, ruleType),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: promotionsQueryKeys.detail(id),
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.all })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
|
||||
@@ -981,15 +981,15 @@
|
||||
"conditions": {
|
||||
"rules": {
|
||||
"title": "Who can use this code?",
|
||||
"description": "Is the customer allowed to add the promotion code? Discount code can be used by all customers if left untouched. Choose between attributes, operators, and values to set up the conditions."
|
||||
"description": "Is the customer allowed to add the promotion code? Discount code can be used by all customers if left untouched."
|
||||
},
|
||||
"target-rules": {
|
||||
"title": "What needs to be in the cart to unlock the promotion?",
|
||||
"description": "If these conditions match, we enable a promotion action on the target items. Choose between attributes, operators, and values to set up the conditions."
|
||||
"title": "What will the promotion be applied to?",
|
||||
"description": "The promotion will be applied to items that match the following conditions"
|
||||
},
|
||||
"buy-rules": {
|
||||
"title": "What will the promotion be applied to?",
|
||||
"description": "The promotion will be applied to items that match these conditions"
|
||||
"title": "What needs to be in the cart to unlock the promotion?",
|
||||
"description": "If these conditions match, we enable the promotion on the target items."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -81,15 +81,23 @@ async function removePromotionRules(
|
||||
)
|
||||
}
|
||||
|
||||
async function listPromotionRules(id: string | null, ruleType: string) {
|
||||
async function listPromotionRules(
|
||||
id: string | null,
|
||||
ruleType: string,
|
||||
query?: Record<string, string>
|
||||
) {
|
||||
return getRequest<PromotionRuleAttributesListRes>(
|
||||
`/admin/promotions/${id}/${ruleType}`
|
||||
`/admin/promotions/${id}/${ruleType}`,
|
||||
query
|
||||
)
|
||||
}
|
||||
|
||||
async function listPromotionRuleAttributes(ruleType: string) {
|
||||
async function listPromotionRuleAttributes(
|
||||
ruleType: string,
|
||||
promotionType?: string
|
||||
) {
|
||||
return getRequest<PromotionRuleAttributesListRes>(
|
||||
`/admin/promotions/rule-attribute-options/${ruleType}`
|
||||
`/admin/promotions/rule-attribute-options/${ruleType}?promotion_type=${promotionType}`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,25 +3,23 @@ import { PromotionDTO, PromotionRuleDTO } from "@medusajs/types"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import i18n from "i18next"
|
||||
import { useState } from "react"
|
||||
import { useFieldArray, useForm } from "react-hook-form"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { RouteDrawer } from "../../../../../../components/route-modal"
|
||||
import { RuleTypeValues } from "../../edit-rules"
|
||||
import { RulesFormField } from "../rules-form-field"
|
||||
import { getDisguisedRules } from "./utils"
|
||||
|
||||
type EditPromotionFormProps = {
|
||||
promotion: PromotionDTO
|
||||
rules: PromotionRuleDTO[]
|
||||
ruleType: RuleTypeValues
|
||||
attributes: any[]
|
||||
operators: any[]
|
||||
handleSubmit: any
|
||||
isSubmitting: boolean
|
||||
}
|
||||
|
||||
const EditRules = zod.object({
|
||||
type: zod.string().optional(),
|
||||
rules: zod.array(
|
||||
zod.object({
|
||||
id: zod.string().optional(),
|
||||
@@ -39,6 +37,7 @@ const EditRules = zod.object({
|
||||
.min(1, { message: i18n.t("promotions.form.required") }),
|
||||
]),
|
||||
required: zod.boolean().optional(),
|
||||
disguised: zod.boolean().optional(),
|
||||
field_type: zod.string().optional(),
|
||||
})
|
||||
),
|
||||
@@ -46,41 +45,18 @@ const EditRules = zod.object({
|
||||
|
||||
export const EditRulesForm = ({
|
||||
promotion,
|
||||
rules,
|
||||
ruleType,
|
||||
attributes,
|
||||
operators,
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
}: EditPromotionFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const requiredAttributes = attributes?.filter((ra) => ra.required) || []
|
||||
const requiredAttributeValues = requiredAttributes?.map((ra) => ra.value)
|
||||
const disguisedRules =
|
||||
getDisguisedRules(promotion, requiredAttributes, ruleType) || []
|
||||
const [rulesToRemove, setRulesToRemove] = useState([])
|
||||
|
||||
const form = useForm<zod.infer<typeof EditRules>>({
|
||||
defaultValues: {
|
||||
rules: [...disguisedRules, ...rules].map((rule) => ({
|
||||
id: rule.id,
|
||||
required: requiredAttributeValues.includes(rule.attribute),
|
||||
attribute: rule.attribute!,
|
||||
operator: rule.operator!,
|
||||
values: Array.isArray(rule?.values)
|
||||
? rule?.values?.map((v: any) => v.value!)
|
||||
: rule.values!,
|
||||
})),
|
||||
},
|
||||
defaultValues: { rules: [], type: promotion.type },
|
||||
resolver: zodResolver(EditRules),
|
||||
})
|
||||
|
||||
const { fields, append, remove, update } = useFieldArray({
|
||||
control: form.control,
|
||||
name: "rules",
|
||||
keyName: "rules_id",
|
||||
})
|
||||
|
||||
const handleFormSubmit = form.handleSubmit(handleSubmit(rulesToRemove))
|
||||
|
||||
return (
|
||||
@@ -90,14 +66,9 @@ export const EditRulesForm = ({
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={ruleType}
|
||||
attributes={attributes}
|
||||
operators={operators}
|
||||
fields={fields}
|
||||
setRulesToRemove={setRulesToRemove}
|
||||
rulesToRemove={rulesToRemove}
|
||||
appendRule={append}
|
||||
removeRule={remove}
|
||||
updateRule={update}
|
||||
promotionId={promotion.id}
|
||||
/>
|
||||
</RouteDrawer.Body>
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { RuleType } from "../../edit-rules"
|
||||
|
||||
// We are disguising couple of database columns as rules here, namely
|
||||
// apply_to_quantity and buy_rules_min_quantity.
|
||||
// We need to transform the database value into a disugised "rule" shape
|
||||
// for the form
|
||||
export function getDisguisedRules(
|
||||
promotion: PromotionDTO,
|
||||
requiredAttributes: any[],
|
||||
ruleType: string
|
||||
) {
|
||||
if (ruleType === RuleType.RULES && !requiredAttributes?.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
const applyToQuantityRule = requiredAttributes.find(
|
||||
(attr) => attr.id === "apply_to_quantity"
|
||||
)
|
||||
|
||||
const buyRulesMinQuantityRule = requiredAttributes.find(
|
||||
(attr) => attr.id === "buy_rules_min_quantity"
|
||||
)
|
||||
|
||||
const currencyCodeRule = requiredAttributes.find(
|
||||
(attr) => attr.id === "currency_code"
|
||||
)
|
||||
|
||||
if (ruleType === RuleType.RULES) {
|
||||
return [
|
||||
{
|
||||
id: "currency_code",
|
||||
attribute: "currency_code",
|
||||
operator: "eq",
|
||||
required: currencyCodeRule?.required,
|
||||
values: promotion?.application_method?.currency_code?.toLowerCase(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (ruleType === RuleType.TARGET_RULES) {
|
||||
return [
|
||||
{
|
||||
id: "apply_to_quantity",
|
||||
attribute: "apply_to_quantity",
|
||||
operator: "eq",
|
||||
required: applyToQuantityRule?.required,
|
||||
values: promotion?.application_method?.apply_to_quantity,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (ruleType === RuleType.BUY_RULES) {
|
||||
return [
|
||||
{
|
||||
id: "buy_rules_min_quantity",
|
||||
attribute: "buy_rules_min_quantity",
|
||||
operator: "eq",
|
||||
required: buyRulesMinQuantityRule?.required,
|
||||
values: [
|
||||
{ value: promotion?.application_method?.buy_rules_min_quantity },
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,19 @@ import {
|
||||
CreatePromotionRuleDTO,
|
||||
PromotionDTO,
|
||||
PromotionRuleDTO,
|
||||
PromotionRuleOperatorValues,
|
||||
PromotionRuleResponse,
|
||||
} from "@medusajs/types"
|
||||
import { useRouteModal } from "../../../../../../components/route-modal"
|
||||
import {
|
||||
usePromotionAddRules,
|
||||
usePromotionRemoveRules,
|
||||
usePromotionRuleAttributes,
|
||||
usePromotionRuleOperators,
|
||||
usePromotionUpdateRules,
|
||||
useUpdatePromotion,
|
||||
} from "../../../../../../hooks/api/promotions"
|
||||
import { RuleTypeValues } from "../../edit-rules"
|
||||
import { EditRulesForm } from "../edit-rules-form"
|
||||
import { getDisguisedRules } from "../edit-rules-form/utils"
|
||||
import { getRuleValue } from "./utils"
|
||||
|
||||
type EditPromotionFormProps = {
|
||||
promotion: PromotionDTO
|
||||
@@ -28,26 +28,6 @@ export const EditRulesWrapper = ({
|
||||
ruleType,
|
||||
}: EditPromotionFormProps) => {
|
||||
const { handleSuccess } = useRouteModal()
|
||||
const {
|
||||
attributes,
|
||||
isError: isAttributesError,
|
||||
error: attributesError,
|
||||
} = usePromotionRuleAttributes(ruleType!)
|
||||
|
||||
const {
|
||||
operators,
|
||||
isError: isOperatorsError,
|
||||
error: operatorsError,
|
||||
} = usePromotionRuleOperators()
|
||||
|
||||
if (isAttributesError || isOperatorsError) {
|
||||
throw attributesError || operatorsError
|
||||
}
|
||||
|
||||
const requiredAttributes = attributes?.filter((ra) => ra.required) || []
|
||||
const disguisedRules =
|
||||
getDisguisedRules(promotion, requiredAttributes, ruleType) || []
|
||||
|
||||
const { mutateAsync: updatePromotion } = useUpdatePromotion(promotion.id)
|
||||
const { mutateAsync: addPromotionRules } = usePromotionAddRules(
|
||||
promotion.id,
|
||||
@@ -63,33 +43,21 @@ export const EditRulesWrapper = ({
|
||||
usePromotionUpdateRules(promotion.id, ruleType)
|
||||
|
||||
const handleSubmit = (rulesToRemove?: { id: string }[]) => {
|
||||
return async function (data: { rules: PromotionRuleDTO[] }) {
|
||||
return async function (data: { rules: PromotionRuleResponse[] }) {
|
||||
const applicationMethodData: Record<any, any> = {}
|
||||
const { rules: allRules = [] } = data
|
||||
const disguisedRulesData = allRules.filter((rule) =>
|
||||
disguisedRules.map((rule) => rule.id).includes(rule.id!)
|
||||
)
|
||||
const disguisedRules = allRules.filter((rule) => rule.disguised)
|
||||
|
||||
// For all the rules that were disguised, convert them to actual values in the
|
||||
// database, they are currently all under application_method. If more of these are coming
|
||||
// up, abstract this away.
|
||||
for (const rule of disguisedRulesData) {
|
||||
const currentAttribute = attributes?.find(
|
||||
(attr) => attr.value === rule.attribute
|
||||
)
|
||||
|
||||
applicationMethodData[rule.id!] =
|
||||
currentAttribute?.field_type === "number"
|
||||
? parseInt(rule.values as unknown as string)
|
||||
: rule.values
|
||||
for (const rule of disguisedRules) {
|
||||
applicationMethodData[rule.attribute] = getRuleValue(rule)
|
||||
}
|
||||
|
||||
// This variable will contain the rules that are actual rule objects, without the disguised
|
||||
// objects
|
||||
const rulesData = allRules.filter(
|
||||
(rule) => !disguisedRules.map((rule) => rule.id).includes(rule.id!)
|
||||
)
|
||||
|
||||
const rulesData = allRules.filter((rule) => !rule.disguised)
|
||||
const rulesToCreate: CreatePromotionRuleDTO[] = rulesData.filter(
|
||||
(rule) => !("id" in rule)
|
||||
)
|
||||
@@ -121,11 +89,11 @@ export const EditRulesWrapper = ({
|
||||
|
||||
rulesToUpdate.length &&
|
||||
(await updatePromotionRules({
|
||||
rules: rulesToUpdate.map((rule: PromotionRuleDTO) => {
|
||||
rules: rulesToUpdate.map((rule: PromotionRuleResponse) => {
|
||||
return {
|
||||
id: rule.id!,
|
||||
attribute: rule.attribute,
|
||||
operator: rule.operator,
|
||||
operator: rule.operator as PromotionRuleOperatorValues,
|
||||
values: rule.values as unknown as string | string[],
|
||||
}
|
||||
}),
|
||||
@@ -135,17 +103,13 @@ export const EditRulesWrapper = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes && operators) {
|
||||
return (
|
||||
<EditRulesForm
|
||||
promotion={promotion}
|
||||
rules={rules}
|
||||
ruleType={ruleType}
|
||||
attributes={attributes}
|
||||
operators={operators}
|
||||
handleSubmit={handleSubmit}
|
||||
isSubmitting={isPending}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<EditRulesForm
|
||||
promotion={promotion}
|
||||
rules={rules}
|
||||
ruleType={ruleType}
|
||||
handleSubmit={handleSubmit}
|
||||
isSubmitting={isPending}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { PromotionRuleResponse } from "@medusajs/types"
|
||||
|
||||
export const getRuleValue = (rule: PromotionRuleResponse) => {
|
||||
if (rule.field_type === "number") {
|
||||
return parseInt(rule.values as unknown as string)
|
||||
}
|
||||
|
||||
if (rule.field_type === "select") {
|
||||
return rule.values[0]
|
||||
}
|
||||
|
||||
return rule.values
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export const requiredProductRule = {
|
||||
id: "product",
|
||||
attribute: "items.product.id",
|
||||
attribute_label: "Product",
|
||||
operator: "eq",
|
||||
operator_label: "Equal",
|
||||
values: [],
|
||||
required: true,
|
||||
field_type: "select",
|
||||
disguised: false,
|
||||
}
|
||||
@@ -1,31 +1,23 @@
|
||||
import { XMarkMini } from "@medusajs/icons"
|
||||
import {
|
||||
RuleAttributeOptionsResponse,
|
||||
RuleOperatorOptionsResponse,
|
||||
} from "@medusajs/types"
|
||||
import { PromotionRuleResponse } from "@medusajs/types"
|
||||
import { Badge, Button, Heading, Select, Text } from "@medusajs/ui"
|
||||
import { Fragment } from "react"
|
||||
import {
|
||||
FieldValues,
|
||||
Path,
|
||||
UseFieldArrayAppend,
|
||||
UseFieldArrayRemove,
|
||||
UseFieldArrayUpdate,
|
||||
UseFormReturn,
|
||||
} from "react-hook-form"
|
||||
import { Fragment, useEffect } from "react"
|
||||
import { useFieldArray, UseFormReturn, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Form } from "../../../../../../components/common/form"
|
||||
import {
|
||||
usePromotionRuleAttributes,
|
||||
usePromotionRuleOperators,
|
||||
usePromotionRules,
|
||||
} from "../../../../../../hooks/api/promotions"
|
||||
import { CreatePromotionSchemaType } from "../../../../promotion-create/components/create-promotion-form/form-schema"
|
||||
import { RuleValueFormField } from "../rule-value-form-field"
|
||||
import { requiredProductRule } from "./constants"
|
||||
|
||||
type RulesFormFieldType<TSchema extends FieldValues> = {
|
||||
form: UseFormReturn<TSchema>
|
||||
type RulesFormFieldType = {
|
||||
promotionId?: string
|
||||
form: UseFormReturn<CreatePromotionSchemaType>
|
||||
ruleType: "rules" | "target-rules" | "buy-rules"
|
||||
fields: any[]
|
||||
attributes: RuleAttributeOptionsResponse[]
|
||||
operators: RuleOperatorOptionsResponse[]
|
||||
removeRule: UseFieldArrayRemove
|
||||
updateRule: UseFieldArrayUpdate<TSchema>
|
||||
appendRule: UseFieldArrayAppend<TSchema>
|
||||
setRulesToRemove?: any
|
||||
rulesToRemove?: any
|
||||
scope?:
|
||||
@@ -34,20 +26,91 @@ type RulesFormFieldType<TSchema extends FieldValues> = {
|
||||
| "application_method.target_rules"
|
||||
}
|
||||
|
||||
export const RulesFormField = <TSchema extends FieldValues>({
|
||||
const generateRuleAttributes = (rules?: PromotionRuleResponse[]) =>
|
||||
(rules || []).map((rule) => ({
|
||||
id: rule.id,
|
||||
required: rule.required,
|
||||
field_type: rule.field_type,
|
||||
disguised: rule.disguised,
|
||||
attribute: rule.attribute!,
|
||||
operator: rule.operator!,
|
||||
values:
|
||||
rule.field_type === "number"
|
||||
? rule.values
|
||||
: rule?.values?.map((v: { value: string }) => v.value!),
|
||||
}))
|
||||
|
||||
export const RulesFormField = ({
|
||||
form,
|
||||
ruleType,
|
||||
fields,
|
||||
attributes,
|
||||
operators,
|
||||
removeRule,
|
||||
updateRule,
|
||||
appendRule,
|
||||
setRulesToRemove,
|
||||
rulesToRemove,
|
||||
scope = "rules",
|
||||
}: RulesFormFieldType<TSchema>) => {
|
||||
promotionId,
|
||||
}: RulesFormFieldType) => {
|
||||
const { t } = useTranslation()
|
||||
const formData = form.getValues()
|
||||
const { attributes } = usePromotionRuleAttributes(ruleType, formData.type)
|
||||
const { operators } = usePromotionRuleOperators()
|
||||
|
||||
const { fields, append, remove, update, replace } = useFieldArray({
|
||||
control: form.control,
|
||||
name: scope,
|
||||
keyName: scope,
|
||||
})
|
||||
|
||||
const promotionType: string = useWatch({
|
||||
control: form.control,
|
||||
name: "type",
|
||||
})
|
||||
|
||||
const query: Record<string, string> = promotionType
|
||||
? { promotion_type: promotionType }
|
||||
: {}
|
||||
|
||||
const { rules, isLoading } = usePromotionRules(
|
||||
promotionId || null,
|
||||
ruleType,
|
||||
query,
|
||||
{
|
||||
enabled: !!promotionType,
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
|
||||
if (ruleType === "rules" && !fields.length) {
|
||||
replace(generateRuleAttributes(rules) as any)
|
||||
}
|
||||
|
||||
if (ruleType === "rules" && promotionType === "standard") {
|
||||
form.resetField("application_method.buy_rules")
|
||||
form.resetField("application_method.target_rules")
|
||||
}
|
||||
|
||||
if (
|
||||
["buy-rules", "target-rules"].includes(ruleType) &&
|
||||
promotionType === "standard"
|
||||
) {
|
||||
form.resetField(scope)
|
||||
replace([])
|
||||
}
|
||||
|
||||
if (
|
||||
["buy-rules", "target-rules"].includes(ruleType) &&
|
||||
promotionType === "buyget"
|
||||
) {
|
||||
form.resetField(scope)
|
||||
const rulesToAppend = promotionId
|
||||
? rules
|
||||
: [...rules, requiredProductRule]
|
||||
|
||||
replace(generateRuleAttributes(rulesToAppend) as any)
|
||||
}
|
||||
}, [promotionType, isLoading])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
@@ -62,17 +125,17 @@ export const RulesFormField = <TSchema extends FieldValues>({
|
||||
{fields.map((fieldRule: any, index) => {
|
||||
const identifier = fieldRule.id
|
||||
const { ref: attributeRef, ...attributeField } = form.register(
|
||||
`${scope}.${index}.attribute` as Path<TSchema>
|
||||
`${scope}.${index}.attribute`
|
||||
)
|
||||
const { ref: operatorRef, ...operatorsField } = form.register(
|
||||
`${scope}.${index}.operator` as Path<TSchema>
|
||||
`${scope}.${index}.operator`
|
||||
)
|
||||
const { ref: valuesRef, ...valuesField } = form.register(
|
||||
`${scope}.${index}.values` as Path<TSchema>
|
||||
`${scope}.${index}.values`
|
||||
)
|
||||
|
||||
return (
|
||||
<Fragment key={`${fieldRule.id}.${index}`}>
|
||||
<Fragment key={`${fieldRule.id}.${index}.${fieldRule.attribute}`}>
|
||||
<div className="bg-ui-bg-subtle border-ui-border-base flex flex-row gap-2 rounded-xl border px-2 py-2">
|
||||
<div className="grow">
|
||||
<Form.Field
|
||||
@@ -102,10 +165,10 @@ export const RulesFormField = <TSchema extends FieldValues>({
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(e) => {
|
||||
updateRule(index, { ...fieldRule, values: [] })
|
||||
update(index, { ...fieldRule, values: [] })
|
||||
onChange(e)
|
||||
}}
|
||||
disabled={fieldRule.required}
|
||||
disabled={fieldRule.disguised}
|
||||
>
|
||||
<Select.Trigger
|
||||
ref={attributeRef}
|
||||
@@ -149,7 +212,7 @@ export const RulesFormField = <TSchema extends FieldValues>({
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={onChange}
|
||||
disabled={fieldRule.required}
|
||||
disabled={fieldRule.disguised}
|
||||
>
|
||||
<Select.Trigger
|
||||
ref={operatorRef}
|
||||
@@ -203,7 +266,7 @@ export const RulesFormField = <TSchema extends FieldValues>({
|
||||
setRulesToRemove &&
|
||||
setRulesToRemove([...rulesToRemove, fieldRule])
|
||||
|
||||
removeRule(index)
|
||||
remove(index)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -229,7 +292,7 @@ export const RulesFormField = <TSchema extends FieldValues>({
|
||||
variant="secondary"
|
||||
className="inline-block"
|
||||
onClick={() => {
|
||||
appendRule({
|
||||
append({
|
||||
attribute: "",
|
||||
operator: "",
|
||||
values: [],
|
||||
@@ -251,7 +314,7 @@ export const RulesFormField = <TSchema extends FieldValues>({
|
||||
|
||||
setRulesToRemove &&
|
||||
setRulesToRemove(fields.filter((field: any) => !field.required))
|
||||
removeRule(indicesToRemove)
|
||||
remove(indicesToRemove)
|
||||
}}
|
||||
>
|
||||
{t("promotions.fields.clearAll")}
|
||||
|
||||
@@ -11,16 +11,11 @@ import {
|
||||
Text,
|
||||
} from "@medusajs/ui"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useFieldArray, useForm, useWatch } from "react-hook-form"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
PromotionRuleOperatorValues,
|
||||
PromotionRuleResponse,
|
||||
RuleAttributeOptionsResponse,
|
||||
RuleOperatorOptionsResponse,
|
||||
} from "@medusajs/types"
|
||||
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
||||
import { Divider } from "../../../../../components/common/divider"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { PercentageInput } from "../../../../../components/inputs/percentage-input"
|
||||
@@ -38,42 +33,13 @@ import { Tab } from "./constants"
|
||||
import { CreatePromotionSchema } from "./form-schema"
|
||||
import { templates } from "./templates"
|
||||
|
||||
type CreatePromotionFormProps = {
|
||||
ruleAttributes: RuleAttributeOptionsResponse[]
|
||||
targetRuleAttributes: RuleAttributeOptionsResponse[]
|
||||
buyRuleAttributes: RuleAttributeOptionsResponse[]
|
||||
operators: RuleOperatorOptionsResponse[]
|
||||
rules: PromotionRuleResponse[]
|
||||
targetRules: PromotionRuleResponse[]
|
||||
buyRules: PromotionRuleResponse[]
|
||||
}
|
||||
|
||||
export const CreatePromotionForm = ({
|
||||
ruleAttributes,
|
||||
targetRuleAttributes,
|
||||
buyRuleAttributes,
|
||||
operators,
|
||||
rules,
|
||||
targetRules,
|
||||
buyRules,
|
||||
}: CreatePromotionFormProps) => {
|
||||
export const CreatePromotionForm = () => {
|
||||
const [tab, setTab] = useState<Tab>(Tab.TYPE)
|
||||
const [detailsValidated, setDetailsValidated] = useState(false)
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const generateRuleAttributes = (rules: PromotionRuleResponse[]) =>
|
||||
rules.map((rule) => ({
|
||||
id: rule.id,
|
||||
required: rule.required,
|
||||
field_type: rule.field_type,
|
||||
disguised: rule.disguised,
|
||||
attribute: rule.attribute!,
|
||||
operator: rule.operator!,
|
||||
values: rule?.values?.map((v: { value: string }) => v.value!),
|
||||
}))
|
||||
|
||||
const form = useForm<z.infer<typeof CreatePromotionSchema>>({
|
||||
defaultValues: {
|
||||
campaign_id: undefined,
|
||||
@@ -82,53 +48,20 @@ export const CreatePromotionForm = ({
|
||||
is_automatic: "false",
|
||||
code: "",
|
||||
type: "standard",
|
||||
rules: generateRuleAttributes(rules),
|
||||
rules: [],
|
||||
application_method: {
|
||||
allocation: "each",
|
||||
type: "fixed",
|
||||
target_type: "items",
|
||||
max_quantity: 1,
|
||||
target_rules: generateRuleAttributes(targetRules),
|
||||
buy_rules: generateRuleAttributes(buyRules),
|
||||
target_rules: [],
|
||||
buy_rules: [],
|
||||
},
|
||||
campaign: undefined,
|
||||
},
|
||||
resolver: zodResolver(CreatePromotionSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
fields: ruleFields,
|
||||
append: appendRule,
|
||||
remove: removeRule,
|
||||
update: updateRule,
|
||||
} = useFieldArray({
|
||||
control: form.control,
|
||||
name: "rules",
|
||||
keyName: "rules_id",
|
||||
})
|
||||
|
||||
const {
|
||||
fields: targetRuleFields,
|
||||
append: appendTargetRule,
|
||||
remove: removeTargetRule,
|
||||
update: updateTargetRule,
|
||||
} = useFieldArray({
|
||||
control: form.control,
|
||||
name: "application_method.target_rules",
|
||||
keyName: "target_rules_id",
|
||||
})
|
||||
|
||||
const {
|
||||
fields: buyRuleFields,
|
||||
append: appendBuyRule,
|
||||
remove: removeBuyRule,
|
||||
update: updateBuyRule,
|
||||
} = useFieldArray({
|
||||
control: form.control,
|
||||
name: "application_method.buy_rules",
|
||||
keyName: "buy_rules_id",
|
||||
})
|
||||
|
||||
const { mutateAsync: createPromotion } = useCreatePromotion()
|
||||
|
||||
const handleSubmit = form.handleSubmit(
|
||||
@@ -265,14 +198,11 @@ export const CreatePromotionForm = ({
|
||||
})
|
||||
|
||||
const isFixedValueType = watchValueType === "fixed"
|
||||
|
||||
const watchAllocation = useWatch({
|
||||
control: form.control,
|
||||
name: "application_method.allocation",
|
||||
})
|
||||
|
||||
const isAllocationEach = watchAllocation === "each"
|
||||
|
||||
useEffect(() => {
|
||||
if (watchAllocation === "across") {
|
||||
form.setValue("application_method.max_quantity", null)
|
||||
@@ -296,17 +226,6 @@ export const CreatePromotionForm = ({
|
||||
|
||||
const { campaigns } = useCampaigns(campaignQuery)
|
||||
|
||||
useEffect(() => {
|
||||
if (isTypeStandard) {
|
||||
form.setValue("application_method.buy_rules", undefined)
|
||||
} else {
|
||||
form.setValue(
|
||||
"application_method.buy_rules",
|
||||
generateRuleAttributes(buyRules)
|
||||
)
|
||||
}
|
||||
}, [isTypeStandard])
|
||||
|
||||
const detailsProgress = useMemo(() => {
|
||||
if (detailsValidated) {
|
||||
return "completed"
|
||||
@@ -592,16 +511,7 @@ export const CreatePromotionForm = ({
|
||||
|
||||
<Divider />
|
||||
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={"rules"}
|
||||
attributes={ruleAttributes}
|
||||
operators={operators}
|
||||
fields={ruleFields}
|
||||
appendRule={appendRule}
|
||||
removeRule={removeRule}
|
||||
updateRule={updateRule}
|
||||
/>
|
||||
<RulesFormField form={form} ruleType={"rules"} />
|
||||
|
||||
<Divider />
|
||||
|
||||
@@ -756,7 +666,7 @@ export const CreatePromotionForm = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{isTypeStandard && isAllocationEach && (
|
||||
{isTypeStandard && watchAllocation === "each" && (
|
||||
<div className="flex gap-y-4">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
@@ -800,33 +710,23 @@ export const CreatePromotionForm = ({
|
||||
|
||||
<Divider />
|
||||
|
||||
{!isTypeStandard && (
|
||||
<>
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={"buy-rules"}
|
||||
scope="application_method.buy_rules"
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={"target-rules"}
|
||||
attributes={targetRuleAttributes}
|
||||
operators={operators}
|
||||
fields={targetRuleFields}
|
||||
appendRule={appendTargetRule}
|
||||
removeRule={removeTargetRule}
|
||||
updateRule={updateTargetRule}
|
||||
scope="application_method.target_rules"
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
{!isTypeStandard && (
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={"buy-rules"}
|
||||
attributes={buyRuleAttributes}
|
||||
operators={operators}
|
||||
fields={buyRuleFields}
|
||||
appendRule={appendBuyRule}
|
||||
removeRule={removeBuyRule}
|
||||
updateRule={updateBuyRule}
|
||||
scope="application_method.buy_rules"
|
||||
/>
|
||||
)}
|
||||
</ProgressTabs.Content>
|
||||
|
||||
<ProgressTabs.Content
|
||||
|
||||
@@ -32,7 +32,7 @@ export const CreatePromotionSchema = z
|
||||
currency_code: z.string(),
|
||||
max_quantity: z.number().optional().nullable(),
|
||||
target_rules: RuleSchema,
|
||||
buy_rules: RuleSchema.min(2).optional(),
|
||||
buy_rules: RuleSchema,
|
||||
type: z.enum(["fixed", "percentage"]),
|
||||
target_type: z.enum(["order", "shipping_methods", "items"]),
|
||||
}),
|
||||
|
||||
@@ -1,42 +1,6 @@
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import {
|
||||
usePromotionRuleAttributes,
|
||||
usePromotionRuleOperators,
|
||||
usePromotionRules,
|
||||
} from "../../../hooks/api/promotions"
|
||||
import { CreatePromotionForm } from "./components/create-promotion-form/create-promotion-form"
|
||||
|
||||
export const PromotionCreate = () => {
|
||||
const { attributes: ruleAttributes } = usePromotionRuleAttributes("rules")
|
||||
const { attributes: targetRuleAttributes } =
|
||||
usePromotionRuleAttributes("target-rules")
|
||||
const { attributes: buyRuleAttributes } =
|
||||
usePromotionRuleAttributes("buy-rules")
|
||||
|
||||
const { rules } = usePromotionRules(null, "rules")
|
||||
const { rules: targetRules } = usePromotionRules(null, "target-rules")
|
||||
const { rules: buyRules } = usePromotionRules(null, "buy-rules")
|
||||
const { operators } = usePromotionRuleOperators()
|
||||
|
||||
return (
|
||||
<RouteFocusModal>
|
||||
{rules &&
|
||||
buyRules &&
|
||||
targetRules &&
|
||||
operators &&
|
||||
ruleAttributes &&
|
||||
targetRuleAttributes &&
|
||||
buyRuleAttributes && (
|
||||
<CreatePromotionForm
|
||||
ruleAttributes={ruleAttributes}
|
||||
targetRuleAttributes={targetRuleAttributes}
|
||||
buyRuleAttributes={buyRuleAttributes}
|
||||
operators={operators}
|
||||
rules={rules}
|
||||
targetRules={targetRules}
|
||||
buyRules={buyRules}
|
||||
/>
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
return <RouteFocusModal>{<CreatePromotionForm />}</RouteFocusModal>
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@ function RuleBlock({ rule }: RuleProps) {
|
||||
<BadgeListSummary
|
||||
inline
|
||||
className="!txt-compact-small-plus"
|
||||
list={rule.values.map((v) => v.label)}
|
||||
list={
|
||||
rule.field_type === "number"
|
||||
? [rule.values]
|
||||
: rule.values?.map((v) => v.label)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,9 +17,15 @@ export const PromotionDetail = () => {
|
||||
|
||||
const { id } = useParams()
|
||||
const { promotion, isLoading } = usePromotion(id!, { initialData })
|
||||
const { rules } = usePromotionRules(id!, "rules")
|
||||
const { rules: targetRules } = usePromotionRules(id!, "target-rules")
|
||||
const { rules: buyRules } = usePromotionRules(id!, "buy-rules")
|
||||
const query: Record<string, string> = {}
|
||||
|
||||
if (promotion?.type === "buyget") {
|
||||
query.promotion_type = promotion.type
|
||||
}
|
||||
|
||||
const { rules } = usePromotionRules(id!, "rules", query)
|
||||
const { rules: targetRules } = usePromotionRules(id!, "target-rules", query)
|
||||
const { rules: buyRules } = usePromotionRules(id!, "buy-rules", query)
|
||||
|
||||
if (isLoading || !promotion) {
|
||||
return <div>Loading...</div>
|
||||
|
||||
@@ -11,5 +11,5 @@ export interface PromotionRuleResponse {
|
||||
}
|
||||
|
||||
export interface AdminPromotionRuleListResponse {
|
||||
attributes: PromotionRuleResponse[]
|
||||
rules: PromotionRuleResponse[]
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
||||
*/
|
||||
export type PromotionTypeValues = "standard" | "buyget"
|
||||
|
||||
/**
|
||||
* The promotion's possible rule types.
|
||||
*/
|
||||
export type RuleTypeValues = "rules" | "buy-rules" | "target-rules"
|
||||
|
||||
/**
|
||||
* The promotion details.
|
||||
*/
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
MedusaResponse,
|
||||
} from "../../../../../types/routing"
|
||||
import {
|
||||
getRuleAttributesMap,
|
||||
operatorsMap,
|
||||
ruleAttributesMap,
|
||||
ruleQueryConfigurations,
|
||||
validateRuleType,
|
||||
} from "../../utils"
|
||||
@@ -33,7 +33,9 @@ export const GET = async (
|
||||
})
|
||||
|
||||
const [promotion] = await remoteQuery(queryObject)
|
||||
const ruleAttributes = ruleAttributesMap[ruleType]
|
||||
const ruleAttributes = getRuleAttributesMap(
|
||||
promotion?.type || req.query.promotion_type
|
||||
)[ruleType]
|
||||
const promotionRules: any[] = []
|
||||
|
||||
if (dasherizedRuleType === RuleType.RULES) {
|
||||
@@ -49,8 +51,19 @@ export const GET = async (
|
||||
const requiredRules = ruleAttributes.filter((attr) => !!attr.required)
|
||||
|
||||
for (const disguisedRule of disguisedRules) {
|
||||
const value = promotion?.application_method?.[disguisedRule.id]
|
||||
const values = value ? [{ label: value, value }] : []
|
||||
const getValues = () => {
|
||||
const value = promotion?.application_method?.[disguisedRule.id]
|
||||
|
||||
if (disguisedRule.field_type === "number") {
|
||||
return value
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return [{ label: value, value }]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
transformedRules.push({
|
||||
id: undefined,
|
||||
@@ -60,7 +73,7 @@ export const GET = async (
|
||||
hydrate: disguisedRule.hydrate || false,
|
||||
operator: RuleOperator.EQ,
|
||||
operator_label: operatorsMap[RuleOperator.EQ].label,
|
||||
values,
|
||||
values: getValues(),
|
||||
disguised: true,
|
||||
required: true,
|
||||
})
|
||||
@@ -90,7 +103,7 @@ export const GET = async (
|
||||
entryPoint: queryConfig.entryPoint,
|
||||
variables: {
|
||||
filters: {
|
||||
[queryConfig.valueAttr]: promotionRule.values.map((v) => v.value),
|
||||
[queryConfig.valueAttr]: promotionRule.values?.map((v) => v.value),
|
||||
},
|
||||
},
|
||||
fields: [queryConfig.labelAttr, queryConfig.valueAttr],
|
||||
@@ -104,10 +117,11 @@ export const GET = async (
|
||||
])
|
||||
)
|
||||
|
||||
promotionRule.values = promotionRule.values.map((value) => ({
|
||||
value: value.value,
|
||||
label: valueLabelMap.get(value.value) || value.value,
|
||||
}))
|
||||
promotionRule.values =
|
||||
promotionRule.values?.map((value) => ({
|
||||
value: value.value,
|
||||
label: valueLabelMap.get(value.value) || value.value,
|
||||
})) || promotionRule.values
|
||||
|
||||
if (!currentRuleAttribute.hydrate) {
|
||||
transformedRules.push({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { unlessPath } from "../../utils/unless-path"
|
||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||
import { createBatchBody } from "../../utils/validators"
|
||||
@@ -62,9 +63,12 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
method: ["GET"],
|
||||
matcher: "/admin/promotions/:id/:rule_type",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetPromotionRuleTypeParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
unlessPath(
|
||||
/.*\/promotions\/rule-attribute-options/,
|
||||
validateAndTransformQuery(
|
||||
AdminGetPromotionRuleTypeParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
)
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -118,4 +122,14 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/promotions/rule-attribute-options/:rule_type",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetPromotionRuleParams,
|
||||
QueryConfig.listRuleTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -2,17 +2,19 @@ import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../types/routing"
|
||||
import { ruleAttributesMap, validateRuleType } from "../../utils"
|
||||
import { getRuleAttributesMap, validateRuleType } from "../../utils"
|
||||
import { AdminGetPromotionRuleParamsType } from "../../validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
req: AuthenticatedMedusaRequest<AdminGetPromotionRuleParamsType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const { rule_type: ruleType } = req.params
|
||||
|
||||
validateRuleType(ruleType)
|
||||
|
||||
const attributes = ruleAttributesMap[ruleType] || []
|
||||
const attributes =
|
||||
getRuleAttributesMap(req.query.promotion_type as string)[ruleType] || []
|
||||
|
||||
res.json({
|
||||
attributes,
|
||||
|
||||
@@ -11,12 +11,17 @@ import {
|
||||
validateRuleAttribute,
|
||||
validateRuleType,
|
||||
} from "../../../utils"
|
||||
import { AdminGetPromotionRuleParamsType } from "../../../validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
req: AuthenticatedMedusaRequest<AdminGetPromotionRuleParamsType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const { rule_type: ruleType, rule_attribute_id: ruleAttributeId } = req.params
|
||||
const {
|
||||
rule_type: ruleType,
|
||||
rule_attribute_id: ruleAttributeId,
|
||||
promotion_type: promotionType,
|
||||
} = req.params
|
||||
const queryConfig = ruleQueryConfigurations[ruleAttributeId]
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const filterableFields = req.filterableFields
|
||||
@@ -28,7 +33,7 @@ export const GET = async (
|
||||
}
|
||||
|
||||
validateRuleType(ruleType)
|
||||
validateRuleAttribute(ruleType, ruleAttributeId)
|
||||
validateRuleAttribute(promotionType, ruleType, ruleAttributeId)
|
||||
|
||||
const { rows } = await remoteQuery(
|
||||
remoteQueryObjectFromString({
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import { PromotionType } from "@medusajs/utils"
|
||||
|
||||
export enum DisguisedRule {
|
||||
APPLY_TO_QUANTITY = "apply_to_quantity",
|
||||
BUY_RULES_MIN_QUANTITY = "buy_rules_min_quantity",
|
||||
CURRENCY_CODE = "currency_code",
|
||||
}
|
||||
|
||||
export const disguisedRulesMap = {
|
||||
[DisguisedRule.APPLY_TO_QUANTITY]: {
|
||||
relation: "application_method",
|
||||
},
|
||||
[DisguisedRule.BUY_RULES_MIN_QUANTITY]: {
|
||||
relation: "application_method",
|
||||
},
|
||||
[DisguisedRule.CURRENCY_CODE]: {
|
||||
relation: "application_method",
|
||||
},
|
||||
}
|
||||
|
||||
const ruleAttributes = [
|
||||
{
|
||||
id: DisguisedRule.CURRENCY_CODE,
|
||||
@@ -94,7 +84,7 @@ const commonAttributes = [
|
||||
},
|
||||
]
|
||||
|
||||
const buyRuleAttributes = [
|
||||
const buyGetBuyRules = [
|
||||
{
|
||||
id: DisguisedRule.BUY_RULES_MIN_QUANTITY,
|
||||
value: DisguisedRule.BUY_RULES_MIN_QUANTITY,
|
||||
@@ -103,10 +93,9 @@ const buyRuleAttributes = [
|
||||
required: true,
|
||||
disguised: true,
|
||||
},
|
||||
...commonAttributes,
|
||||
]
|
||||
|
||||
const targetRuleAttributes = [
|
||||
const buyGetTargetRules = [
|
||||
{
|
||||
id: DisguisedRule.APPLY_TO_QUANTITY,
|
||||
value: DisguisedRule.APPLY_TO_QUANTITY,
|
||||
@@ -115,11 +104,19 @@ const targetRuleAttributes = [
|
||||
required: true,
|
||||
disguised: true,
|
||||
},
|
||||
...commonAttributes,
|
||||
]
|
||||
|
||||
export const ruleAttributesMap = {
|
||||
rules: ruleAttributes,
|
||||
"target-rules": targetRuleAttributes,
|
||||
"buy-rules": buyRuleAttributes,
|
||||
export const getRuleAttributesMap = (promotionType?: string) => {
|
||||
const map = {
|
||||
rules: [...ruleAttributes],
|
||||
"target-rules": [...commonAttributes],
|
||||
"buy-rules": [...commonAttributes],
|
||||
}
|
||||
|
||||
if (promotionType === PromotionType.BUYGET) {
|
||||
map["buy-rules"].push(...buyGetBuyRules)
|
||||
map["target-rules"].push(...buyGetTargetRules)
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import { ruleAttributesMap } from "./rule-attributes-map"
|
||||
import { getRuleAttributesMap } from "./rule-attributes-map"
|
||||
|
||||
export function validateRuleAttribute(
|
||||
promotionType: string | undefined,
|
||||
ruleType: string,
|
||||
ruleAttributeId: string
|
||||
) {
|
||||
const ruleAttributes = ruleAttributesMap[ruleType] || []
|
||||
const ruleAttributes = getRuleAttributesMap(promotionType)[ruleType] || []
|
||||
const ruleAttribute = ruleAttributes.find((obj) => obj.id === ruleAttributeId)
|
||||
|
||||
if (!ruleAttribute) {
|
||||
|
||||
@@ -47,12 +47,18 @@ export const AdminGetPromotionsParams = createFindParams({
|
||||
export type AdminGetPromotionRuleParamsType = z.infer<
|
||||
typeof AdminGetPromotionRuleParams
|
||||
>
|
||||
export const AdminGetPromotionRuleParams = createSelectParams()
|
||||
export const AdminGetPromotionRuleParams = z.object({
|
||||
promotion_type: z.string().optional(),
|
||||
})
|
||||
|
||||
export type AdminGetPromotionRuleTypeParamsType = z.infer<
|
||||
typeof AdminGetPromotionRuleTypeParams
|
||||
>
|
||||
export const AdminGetPromotionRuleTypeParams = createSelectParams()
|
||||
export const AdminGetPromotionRuleTypeParams = createSelectParams().merge(
|
||||
z.object({
|
||||
promotion_type: z.string().optional(),
|
||||
})
|
||||
)
|
||||
|
||||
export type AdminGetPromotionsRuleValueParamsType = z.infer<
|
||||
typeof AdminGetPromotionsRuleValueParams
|
||||
|
||||
Reference in New Issue
Block a user