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