feat(dashboard,types,promotion,medusa): hide fields on promotions depending on templates (#7746)
**what:** - hides different fields depending on the chosen template - remove operator values API - fixes to edit promotion rules - make currency optional for promotion RESOLVES CORE-2297
This commit is contained in:
1 parent
1451112f08
commit
0d04c548f5
24 files changed
+523
-389
No files matched your search
@@ -2,7 +2,6 @@ import { AdminGetPromotionsParams } from "@medusajs/medusa"
|
|||||||
import {
|
import {
|
||||||
AdminPromotionRuleListResponse,
|
AdminPromotionRuleListResponse,
|
||||||
AdminRuleAttributeOptionsListResponse,
|
AdminRuleAttributeOptionsListResponse,
|
||||||
AdminRuleOperatorOptionsListResponse,
|
|
||||||
AdminRuleValueOptionsListResponse,
|
AdminRuleValueOptionsListResponse,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
@@ -49,7 +48,6 @@ export const promotionsQueryKeys = {
|
|||||||
ruleValue,
|
ruleValue,
|
||||||
query,
|
query,
|
||||||
],
|
],
|
||||||
listRuleOperators: () => [PROMOTIONS_QUERY_KEY],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePromotion = (
|
export const usePromotion = (
|
||||||
@@ -107,26 +105,6 @@ export const usePromotions = (
|
|||||||
return { ...data, ...rest }
|
return { ...data, ...rest }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePromotionRuleOperators = (
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<
|
|
||||||
AdminRuleOperatorOptionsListResponse,
|
|
||||||
Error,
|
|
||||||
AdminRuleOperatorOptionsListResponse,
|
|
||||||
QueryKey
|
|
||||||
>,
|
|
||||||
"queryFn" | "queryKey"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
const { data, ...rest } = useQuery({
|
|
||||||
queryKey: promotionsQueryKeys.listRuleOperators(),
|
|
||||||
queryFn: async () => client.promotions.listRuleOperators(),
|
|
||||||
...options,
|
|
||||||
})
|
|
||||||
|
|
||||||
return { ...data, ...rest }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePromotionRuleAttributes = (
|
export const usePromotionRuleAttributes = (
|
||||||
ruleType: string,
|
ruleType: string,
|
||||||
promotionType?: string,
|
promotionType?: string,
|
||||||
|
|||||||
@@ -1151,15 +1151,15 @@
|
|||||||
"description": "The code your customers will enter during checkout."
|
"description": "The code your customers will enter during checkout."
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"title": "Value"
|
"title": "Promotion Value"
|
||||||
},
|
},
|
||||||
"value_type": {
|
"value_type": {
|
||||||
"fixed": {
|
"fixed": {
|
||||||
"title": "Fixed amount",
|
"title": "Promotion Value",
|
||||||
"description": "eg. 100"
|
"description": "eg. 100"
|
||||||
},
|
},
|
||||||
"percentage": {
|
"percentage": {
|
||||||
"title": "Percentage",
|
"title": "Promotion Value",
|
||||||
"description": "eg. 8%"
|
"description": "eg. 8%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
PromotionListRes,
|
PromotionListRes,
|
||||||
PromotionRes,
|
PromotionRes,
|
||||||
PromotionRuleAttributesListRes,
|
PromotionRuleAttributesListRes,
|
||||||
PromotionRuleOperatorsListRes,
|
|
||||||
} from "../../types/api-responses"
|
} from "../../types/api-responses"
|
||||||
import { deleteRequest, getRequest, postRequest } from "./common"
|
import { deleteRequest, getRequest, postRequest } from "./common"
|
||||||
|
|
||||||
@@ -112,12 +111,6 @@ async function listPromotionRuleValues(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listPromotionRuleOperators() {
|
|
||||||
return getRequest<PromotionRuleOperatorsListRes>(
|
|
||||||
`/admin/promotions/rule-operator-options`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const promotions = {
|
export const promotions = {
|
||||||
retrieve: retrievePromotion,
|
retrieve: retrievePromotion,
|
||||||
list: listPromotions,
|
list: listPromotions,
|
||||||
@@ -129,6 +122,5 @@ export const promotions = {
|
|||||||
updateRules: updatePromotionRules,
|
updateRules: updatePromotionRules,
|
||||||
listRules: listPromotionRules,
|
listRules: listPromotionRules,
|
||||||
listRuleAttributes: listPromotionRuleAttributes,
|
listRuleAttributes: listPromotionRuleAttributes,
|
||||||
listRuleOperators: listPromotionRuleOperators,
|
|
||||||
listRuleValues: listPromotionRuleValues,
|
listRuleValues: listPromotionRuleValues,
|
||||||
}
|
}
|
||||||
+4
-30
@@ -1,14 +1,13 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { PromotionDTO, PromotionRuleDTO } from "@medusajs/types"
|
import { PromotionDTO, PromotionRuleDTO } from "@medusajs/types"
|
||||||
import { Button } from "@medusajs/ui"
|
import { Button } from "@medusajs/ui"
|
||||||
import i18n from "i18next"
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { 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 { 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 { EditRules, EditRulesType } from "./form-schema"
|
||||||
|
|
||||||
type EditPromotionFormProps = {
|
type EditPromotionFormProps = {
|
||||||
promotion: PromotionDTO
|
promotion: PromotionDTO
|
||||||
@@ -18,31 +17,6 @@ type EditPromotionFormProps = {
|
|||||||
isSubmitting: boolean
|
isSubmitting: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditRules = zod.object({
|
|
||||||
type: zod.string().optional(),
|
|
||||||
rules: zod.array(
|
|
||||||
zod.object({
|
|
||||||
id: zod.string().optional(),
|
|
||||||
attribute: zod
|
|
||||||
.string()
|
|
||||||
.min(1, { message: i18n.t("promotions.form.required") }),
|
|
||||||
operator: zod
|
|
||||||
.string()
|
|
||||||
.min(1, { message: i18n.t("promotions.form.required") }),
|
|
||||||
values: zod.union([
|
|
||||||
zod.number().min(1, { message: i18n.t("promotions.form.required") }),
|
|
||||||
zod.string().min(1, { message: i18n.t("promotions.form.required") }),
|
|
||||||
zod
|
|
||||||
.array(zod.string())
|
|
||||||
.min(1, { message: i18n.t("promotions.form.required") }),
|
|
||||||
]),
|
|
||||||
required: zod.boolean().optional(),
|
|
||||||
disguised: zod.boolean().optional(),
|
|
||||||
field_type: zod.string().optional(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const EditRulesForm = ({
|
export const EditRulesForm = ({
|
||||||
promotion,
|
promotion,
|
||||||
ruleType,
|
ruleType,
|
||||||
@@ -52,7 +26,7 @@ export const EditRulesForm = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [rulesToRemove, setRulesToRemove] = useState([])
|
const [rulesToRemove, setRulesToRemove] = useState([])
|
||||||
|
|
||||||
const form = useForm<zod.infer<typeof EditRules>>({
|
const form = useForm<EditRulesType>({
|
||||||
defaultValues: { rules: [], type: promotion.type },
|
defaultValues: { rules: [], type: promotion.type },
|
||||||
resolver: zodResolver(EditRules),
|
resolver: zodResolver(EditRules),
|
||||||
})
|
})
|
||||||
@@ -64,11 +38,11 @@ export const EditRulesForm = ({
|
|||||||
<form onSubmit={handleFormSubmit} className="flex h-full flex-col">
|
<form onSubmit={handleFormSubmit} className="flex h-full flex-col">
|
||||||
<RouteDrawer.Body>
|
<RouteDrawer.Body>
|
||||||
<RulesFormField
|
<RulesFormField
|
||||||
form={form}
|
form={form as any}
|
||||||
ruleType={ruleType}
|
ruleType={ruleType}
|
||||||
setRulesToRemove={setRulesToRemove}
|
setRulesToRemove={setRulesToRemove}
|
||||||
rulesToRemove={rulesToRemove}
|
rulesToRemove={rulesToRemove}
|
||||||
promotionId={promotion.id}
|
promotion={promotion}
|
||||||
/>
|
/>
|
||||||
</RouteDrawer.Body>
|
</RouteDrawer.Body>
|
||||||
|
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
import i18n from "i18next"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const EditRules = z.object({
|
||||||
|
type: z.string().optional(),
|
||||||
|
rules: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
attribute: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: i18n.t("promotions.form.required") }),
|
||||||
|
operator: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: i18n.t("promotions.form.required") }),
|
||||||
|
values: z.union([
|
||||||
|
z.number().min(1, { message: i18n.t("promotions.form.required") }),
|
||||||
|
z.string().min(1, { message: i18n.t("promotions.form.required") }),
|
||||||
|
z
|
||||||
|
.array(z.string())
|
||||||
|
.min(1, { message: i18n.t("promotions.form.required") }),
|
||||||
|
]),
|
||||||
|
required: z.boolean().optional(),
|
||||||
|
disguised: z.boolean().optional(),
|
||||||
|
field_type: z.string().optional(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type EditRulesType = z.infer<typeof EditRules>
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
import { PromotionRuleResponse } from "@medusajs/types"
|
||||||
|
|
||||||
|
export 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.operator === "eq"
|
||||||
|
? typeof rule.values === "object"
|
||||||
|
? rule.values[0]?.value
|
||||||
|
: rule.values
|
||||||
|
: rule?.values?.map((v: { value: string }) => v.value!),
|
||||||
|
}))
|
||||||
+11
-3
@@ -42,11 +42,15 @@ export const EditRulesWrapper = ({
|
|||||||
const { mutateAsync: updatePromotionRules, isPending } =
|
const { mutateAsync: updatePromotionRules, isPending } =
|
||||||
usePromotionUpdateRules(promotion.id, ruleType)
|
usePromotionUpdateRules(promotion.id, ruleType)
|
||||||
|
|
||||||
const handleSubmit = (rulesToRemove?: { id: string }[]) => {
|
const handleSubmit = (
|
||||||
|
rulesToRemove?: { id: string; disguised: boolean; attribute: string }[]
|
||||||
|
) => {
|
||||||
return async function (data: { rules: PromotionRuleResponse[] }) {
|
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 disguisedRules = allRules.filter((rule) => rule.disguised)
|
const disguisedRules = allRules.filter((rule) => rule.disguised)
|
||||||
|
const disguisedRulesToRemove =
|
||||||
|
rulesToRemove?.filter((r) => r.disguised) || []
|
||||||
|
|
||||||
// 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
|
||||||
@@ -55,6 +59,10 @@ export const EditRulesWrapper = ({
|
|||||||
applicationMethodData[rule.attribute] = getRuleValue(rule)
|
applicationMethodData[rule.attribute] = getRuleValue(rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const rule of disguisedRulesToRemove) {
|
||||||
|
applicationMethodData[rule.attribute] = null
|
||||||
|
}
|
||||||
|
|
||||||
// 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((rule) => !rule.disguised)
|
const rulesData = allRules.filter((rule) => !rule.disguised)
|
||||||
@@ -77,14 +85,14 @@ export const EditRulesWrapper = ({
|
|||||||
return {
|
return {
|
||||||
attribute: rule.attribute,
|
attribute: rule.attribute,
|
||||||
operator: rule.operator,
|
operator: rule.operator,
|
||||||
values: rule.operator === "eq" ? rule.values[0] : rule.values,
|
values: rule.values,
|
||||||
} as any
|
} as any
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
rulesToRemove?.length &&
|
rulesToRemove?.length &&
|
||||||
(await removePromotionRules({
|
(await removePromotionRules({
|
||||||
rule_ids: rulesToRemove.map((r) => r.id!),
|
rule_ids: rulesToRemove.map((r) => r.id).filter(Boolean),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
rulesToUpdate.length &&
|
rulesToUpdate.length &&
|
||||||
|
|||||||
-4
@@ -5,9 +5,5 @@ export const getRuleValue = (rule: PromotionRuleResponse) => {
|
|||||||
return parseInt(rule.values as unknown as string)
|
return parseInt(rule.values as unknown as string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rule.field_type === "select") {
|
|
||||||
return rule.values[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return rule.values
|
return rule.values
|
||||||
}
|
}
|
||||||
+53
-43
@@ -1,5 +1,5 @@
|
|||||||
import { XMarkMini } from "@medusajs/icons"
|
import { XMarkMini } from "@medusajs/icons"
|
||||||
import { PromotionRuleResponse } from "@medusajs/types"
|
import { PromotionDTO } from "@medusajs/types"
|
||||||
import { Badge, Button, Heading, Select, Text } from "@medusajs/ui"
|
import { Badge, Button, Heading, Select, Text } from "@medusajs/ui"
|
||||||
import { Fragment, useEffect } from "react"
|
import { Fragment, useEffect } from "react"
|
||||||
import { useFieldArray, UseFormReturn, useWatch } from "react-hook-form"
|
import { useFieldArray, UseFormReturn, useWatch } from "react-hook-form"
|
||||||
@@ -7,15 +7,15 @@ import { useTranslation } from "react-i18next"
|
|||||||
import { Form } from "../../../../../../components/common/form"
|
import { Form } from "../../../../../../components/common/form"
|
||||||
import {
|
import {
|
||||||
usePromotionRuleAttributes,
|
usePromotionRuleAttributes,
|
||||||
usePromotionRuleOperators,
|
|
||||||
usePromotionRules,
|
usePromotionRules,
|
||||||
} from "../../../../../../hooks/api/promotions"
|
} from "../../../../../../hooks/api/promotions"
|
||||||
import { CreatePromotionSchemaType } from "../../../../promotion-create/components/create-promotion-form/form-schema"
|
import { CreatePromotionSchemaType } from "../../../../promotion-create/components/create-promotion-form/form-schema"
|
||||||
|
import { generateRuleAttributes } from "../edit-rules-form/utils"
|
||||||
import { RuleValueFormField } from "../rule-value-form-field"
|
import { RuleValueFormField } from "../rule-value-form-field"
|
||||||
import { requiredProductRule } from "./constants"
|
import { requiredProductRule } from "./constants"
|
||||||
|
|
||||||
type RulesFormFieldType = {
|
type RulesFormFieldType = {
|
||||||
promotionId?: string
|
promotion?: PromotionDTO
|
||||||
form: UseFormReturn<CreatePromotionSchemaType>
|
form: UseFormReturn<CreatePromotionSchemaType>
|
||||||
ruleType: "rules" | "target-rules" | "buy-rules"
|
ruleType: "rules" | "target-rules" | "buy-rules"
|
||||||
setRulesToRemove?: any
|
setRulesToRemove?: any
|
||||||
@@ -26,32 +26,17 @@ type RulesFormFieldType = {
|
|||||||
| "application_method.target_rules"
|
| "application_method.target_rules"
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = ({
|
export const RulesFormField = ({
|
||||||
form,
|
form,
|
||||||
ruleType,
|
ruleType,
|
||||||
setRulesToRemove,
|
setRulesToRemove,
|
||||||
rulesToRemove,
|
rulesToRemove,
|
||||||
scope = "rules",
|
scope = "rules",
|
||||||
promotionId,
|
promotion,
|
||||||
}: RulesFormFieldType) => {
|
}: RulesFormFieldType) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const formData = form.getValues()
|
const formData = form.getValues()
|
||||||
const { attributes } = usePromotionRuleAttributes(ruleType, formData.type)
|
const { attributes } = usePromotionRuleAttributes(ruleType, formData.type)
|
||||||
const { operators } = usePromotionRuleOperators()
|
|
||||||
|
|
||||||
const { fields, append, remove, update, replace } = useFieldArray({
|
const { fields, append, remove, update, replace } = useFieldArray({
|
||||||
control: form.control,
|
control: form.control,
|
||||||
@@ -59,21 +44,31 @@ export const RulesFormField = ({
|
|||||||
keyName: scope,
|
keyName: scope,
|
||||||
})
|
})
|
||||||
|
|
||||||
const promotionType: string = useWatch({
|
const promotionType = useWatch({
|
||||||
control: form.control,
|
control: form.control,
|
||||||
name: "type",
|
name: "type",
|
||||||
|
defaultValue: promotion?.type,
|
||||||
|
})
|
||||||
|
|
||||||
|
const applicationMethodType = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: "application_method.type",
|
||||||
|
defaultValue: promotion?.application_method?.type,
|
||||||
})
|
})
|
||||||
|
|
||||||
const query: Record<string, string> = promotionType
|
const query: Record<string, string> = promotionType
|
||||||
? { promotion_type: promotionType }
|
? {
|
||||||
|
promotion_type: promotionType,
|
||||||
|
application_method_type: applicationMethodType,
|
||||||
|
}
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
const { rules, isLoading } = usePromotionRules(
|
const { rules, isLoading } = usePromotionRules(
|
||||||
promotionId || null,
|
promotion?.id || null,
|
||||||
ruleType,
|
ruleType,
|
||||||
query,
|
query,
|
||||||
{
|
{
|
||||||
enabled: !!promotionType,
|
enabled: !!promotion?.id || (!!promotionType && !!applicationMethodType),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,12 +99,18 @@ export const RulesFormField = ({
|
|||||||
promotionType === "buyget"
|
promotionType === "buyget"
|
||||||
) {
|
) {
|
||||||
form.resetField(scope)
|
form.resetField(scope)
|
||||||
const rulesToAppend = promotionId
|
const rulesToAppend = promotion?.id
|
||||||
? rules
|
? rules
|
||||||
: [...rules, requiredProductRule]
|
: [...rules, requiredProductRule]
|
||||||
|
|
||||||
replace(generateRuleAttributes(rulesToAppend) as any)
|
replace(generateRuleAttributes(rulesToAppend) as any)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.resetField(scope)
|
||||||
|
|
||||||
|
replace(generateRuleAttributes(rules) as any)
|
||||||
}, [promotionType, isLoading])
|
}, [promotionType, isLoading])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -165,10 +166,18 @@ export const RulesFormField = ({
|
|||||||
<Select
|
<Select
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
update(index, { ...fieldRule, values: [] })
|
const currentAttributeOption =
|
||||||
|
attributeOptions.find((ao) => ao.id === e)
|
||||||
|
|
||||||
|
update(index, {
|
||||||
|
...fieldRule,
|
||||||
|
values: [],
|
||||||
|
disguised:
|
||||||
|
currentAttributeOption?.disguised || false,
|
||||||
|
})
|
||||||
onChange(e)
|
onChange(e)
|
||||||
}}
|
}}
|
||||||
disabled={fieldRule.disguised}
|
disabled={fieldRule.required}
|
||||||
>
|
>
|
||||||
<Select.Trigger
|
<Select.Trigger
|
||||||
ref={attributeRef}
|
ref={attributeRef}
|
||||||
@@ -206,14 +215,14 @@ export const RulesFormField = ({
|
|||||||
key={`${identifier}.${scope}.${operatorsField.name}`}
|
key={`${identifier}.${scope}.${operatorsField.name}`}
|
||||||
{...operatorsField}
|
{...operatorsField}
|
||||||
render={({ field: { onChange, ref, ...field } }) => {
|
render={({ field: { onChange, ref, ...field } }) => {
|
||||||
|
const currentAttributeOption = attributes.find(
|
||||||
|
(attr) => attr.value === fieldRule.attribute
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Item className="basis-1/2">
|
<Form.Item className="basis-1/2">
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Select
|
<Select {...field} onValueChange={onChange}>
|
||||||
{...field}
|
|
||||||
onValueChange={onChange}
|
|
||||||
disabled={fieldRule.disguised}
|
|
||||||
>
|
|
||||||
<Select.Trigger
|
<Select.Trigger
|
||||||
ref={operatorRef}
|
ref={operatorRef}
|
||||||
className="bg-ui-bg-base"
|
className="bg-ui-bg-base"
|
||||||
@@ -222,16 +231,18 @@ export const RulesFormField = ({
|
|||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
|
|
||||||
<Select.Content>
|
<Select.Content>
|
||||||
{operators?.map((c, i) => (
|
{currentAttributeOption?.operators?.map(
|
||||||
<Select.Item
|
(c, i) => (
|
||||||
key={`${identifier}-operator-option-${i}`}
|
<Select.Item
|
||||||
value={c.value}
|
key={`${identifier}-operator-option-${i}`}
|
||||||
>
|
value={c.value}
|
||||||
<span className="text-ui-fg-subtle">
|
>
|
||||||
{c.label}
|
<span className="text-ui-fg-subtle">
|
||||||
</span>
|
{c.label}
|
||||||
</Select.Item>
|
</span>
|
||||||
))}
|
</Select.Item>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
@@ -262,8 +273,7 @@ export const RulesFormField = ({
|
|||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!fieldRule.required) {
|
if (!fieldRule.required) {
|
||||||
fieldRule.id &&
|
setRulesToRemove &&
|
||||||
setRulesToRemove &&
|
|
||||||
setRulesToRemove([...rulesToRemove, fieldRule])
|
setRulesToRemove([...rulesToRemove, fieldRule])
|
||||||
|
|
||||||
remove(index)
|
remove(index)
|
||||||
|
|||||||
+261
-198
@@ -1,6 +1,7 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
clx,
|
clx,
|
||||||
CurrencyInput,
|
CurrencyInput,
|
||||||
@@ -15,7 +16,13 @@ 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 { PromotionRuleOperatorValues } from "@medusajs/types"
|
import {
|
||||||
|
ApplicationMethodAllocationValues,
|
||||||
|
ApplicationMethodTargetTypeValues,
|
||||||
|
ApplicationMethodTypeValues,
|
||||||
|
PromotionRuleOperatorValues,
|
||||||
|
PromotionTypeValues,
|
||||||
|
} 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"
|
||||||
@@ -33,6 +40,25 @@ import { Tab } from "./constants"
|
|||||||
import { CreatePromotionSchema } from "./form-schema"
|
import { CreatePromotionSchema } from "./form-schema"
|
||||||
import { templates } from "./templates"
|
import { templates } from "./templates"
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
campaign_id: undefined,
|
||||||
|
template_id: templates[0].id!,
|
||||||
|
campaign_choice: "none" as "none",
|
||||||
|
is_automatic: "false",
|
||||||
|
code: "",
|
||||||
|
type: "standard" as PromotionTypeValues,
|
||||||
|
rules: [],
|
||||||
|
application_method: {
|
||||||
|
allocation: "each" as ApplicationMethodAllocationValues,
|
||||||
|
type: "fixed" as ApplicationMethodTypeValues,
|
||||||
|
target_type: "items" as ApplicationMethodTargetTypeValues,
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [],
|
||||||
|
buy_rules: [],
|
||||||
|
},
|
||||||
|
campaign: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
export const CreatePromotionForm = () => {
|
export const CreatePromotionForm = () => {
|
||||||
const [tab, setTab] = useState<Tab>(Tab.TYPE)
|
const [tab, setTab] = useState<Tab>(Tab.TYPE)
|
||||||
const [detailsValidated, setDetailsValidated] = useState(false)
|
const [detailsValidated, setDetailsValidated] = useState(false)
|
||||||
@@ -41,24 +67,7 @@ export const CreatePromotionForm = () => {
|
|||||||
const { handleSuccess } = useRouteModal()
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof CreatePromotionSchema>>({
|
const form = useForm<z.infer<typeof CreatePromotionSchema>>({
|
||||||
defaultValues: {
|
defaultValues,
|
||||||
campaign_id: undefined,
|
|
||||||
template_id: templates[0].id!,
|
|
||||||
campaign_choice: "none",
|
|
||||||
is_automatic: "false",
|
|
||||||
code: "",
|
|
||||||
type: "standard",
|
|
||||||
rules: [],
|
|
||||||
application_method: {
|
|
||||||
allocation: "each",
|
|
||||||
type: "fixed",
|
|
||||||
target_type: "items",
|
|
||||||
max_quantity: 1,
|
|
||||||
target_rules: [],
|
|
||||||
buy_rules: [],
|
|
||||||
},
|
|
||||||
campaign: undefined,
|
|
||||||
},
|
|
||||||
resolver: zodResolver(CreatePromotionSchema),
|
resolver: zodResolver(CreatePromotionSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -172,7 +181,7 @@ export const CreatePromotionForm = () => {
|
|||||||
name: "template_id",
|
name: "template_id",
|
||||||
})
|
})
|
||||||
|
|
||||||
useMemo(() => {
|
const currentTemplate = useMemo(() => {
|
||||||
const currentTemplate = templates.find(
|
const currentTemplate = templates.find(
|
||||||
(template) => template.id === watchTemplateId
|
(template) => template.id === watchTemplateId
|
||||||
)
|
)
|
||||||
@@ -181,6 +190,8 @@ export const CreatePromotionForm = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.reset({ ...defaultValues, template_id: watchTemplateId })
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(currentTemplate.defaults)) {
|
for (const [key, value] of Object.entries(currentTemplate.defaults)) {
|
||||||
if (typeof value === "object") {
|
if (typeof value === "object") {
|
||||||
for (const [subKey, subValue] of Object.entries(value)) {
|
for (const [subKey, subValue] of Object.entries(value)) {
|
||||||
@@ -190,6 +201,8 @@ export const CreatePromotionForm = () => {
|
|||||||
form.setValue(key, value)
|
form.setValue(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return currentTemplate
|
||||||
}, [watchTemplateId])
|
}, [watchTemplateId])
|
||||||
|
|
||||||
const watchValueType = useWatch({
|
const watchValueType = useWatch({
|
||||||
@@ -215,6 +228,14 @@ export const CreatePromotionForm = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isTypeStandard = watchType === "standard"
|
const isTypeStandard = watchType === "standard"
|
||||||
|
|
||||||
|
const targetType = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: "application_method.target_type",
|
||||||
|
})
|
||||||
|
|
||||||
|
const isTargetTypeOrder = targetType === "order"
|
||||||
|
|
||||||
const formData = form.getValues()
|
const formData = form.getValues()
|
||||||
let campaignQuery: object = {}
|
let campaignQuery: object = {}
|
||||||
|
|
||||||
@@ -385,9 +406,22 @@ export const CreatePromotionForm = () => {
|
|||||||
|
|
||||||
<ProgressTabs.Content
|
<ProgressTabs.Content
|
||||||
value={Tab.PROMOTION}
|
value={Tab.PROMOTION}
|
||||||
className="flex flex-1 flex-col gap-10"
|
className="flex flex-1 flex-col gap-8"
|
||||||
>
|
>
|
||||||
<Heading level="h2">{t(`promotions.sections.details`)}</Heading>
|
<Heading level="h1" className="text-fg-base">
|
||||||
|
{t(`promotions.sections.details`)}
|
||||||
|
|
||||||
|
{currentTemplate?.title && (
|
||||||
|
<Badge
|
||||||
|
className="ml-2 align-middle"
|
||||||
|
color="grey"
|
||||||
|
size="2xsmall"
|
||||||
|
rounded="full"
|
||||||
|
>
|
||||||
|
{currentTemplate?.title}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Heading>
|
||||||
|
|
||||||
{form.formState.errors.root && (
|
{form.formState.errors.root && (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -471,167 +505,14 @@ export const CreatePromotionForm = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form.Field
|
{!currentTemplate?.hiddenFields?.includes("type") && (
|
||||||
control={form.control}
|
|
||||||
name="type"
|
|
||||||
render={({ field }) => {
|
|
||||||
return (
|
|
||||||
<Form.Item>
|
|
||||||
<Form.Label>{t("promotions.fields.type")}</Form.Label>
|
|
||||||
<Form.Control>
|
|
||||||
<RadioGroup
|
|
||||||
className="flex gap-y-3"
|
|
||||||
{...field}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
>
|
|
||||||
<RadioGroup.ChoiceBox
|
|
||||||
value={"standard"}
|
|
||||||
label={t("promotions.form.type.standard.title")}
|
|
||||||
description={t(
|
|
||||||
"promotions.form.type.standard.description"
|
|
||||||
)}
|
|
||||||
className={clx("basis-1/2")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RadioGroup.ChoiceBox
|
|
||||||
value={"buyget"}
|
|
||||||
label={t("promotions.form.type.buyget.title")}
|
|
||||||
description={t(
|
|
||||||
"promotions.form.type.buyget.description"
|
|
||||||
)}
|
|
||||||
className={clx("basis-1/2")}
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
</Form.Control>
|
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<RulesFormField form={form} ruleType={"rules"} />
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<Form.Field
|
|
||||||
control={form.control}
|
|
||||||
name="application_method.type"
|
|
||||||
render={({ field }) => {
|
|
||||||
return (
|
|
||||||
<Form.Item>
|
|
||||||
<Form.Label>
|
|
||||||
{t("promotions.fields.value_type")}
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Control>
|
|
||||||
<RadioGroup
|
|
||||||
className="flex gap-y-3"
|
|
||||||
{...field}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
>
|
|
||||||
<RadioGroup.ChoiceBox
|
|
||||||
value={"fixed"}
|
|
||||||
label={t("promotions.form.value_type.fixed.title")}
|
|
||||||
description={t(
|
|
||||||
"promotions.form.value_type.fixed.description"
|
|
||||||
)}
|
|
||||||
className={clx("basis-1/2")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RadioGroup.ChoiceBox
|
|
||||||
value={"percentage"}
|
|
||||||
label={t(
|
|
||||||
"promotions.form.value_type.percentage.title"
|
|
||||||
)}
|
|
||||||
description={t(
|
|
||||||
"promotions.form.value_type.percentage.description"
|
|
||||||
)}
|
|
||||||
className={clx("basis-1/2")}
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
</Form.Control>
|
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex gap-y-4">
|
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="application_method.value"
|
name="type"
|
||||||
render={({ field: { onChange, value, ...field } }) => {
|
|
||||||
const currencyCode =
|
|
||||||
form.getValues().application_method.currency_code
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item className="basis-1/2">
|
|
||||||
<Form.Label
|
|
||||||
tooltip={
|
|
||||||
currencyCode || !isFixedValueType
|
|
||||||
? undefined
|
|
||||||
: t("promotions.fields.amount.tooltip")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{isFixedValueType
|
|
||||||
? t("fields.amount")
|
|
||||||
: t("fields.percentage")}
|
|
||||||
</Form.Label>
|
|
||||||
|
|
||||||
<Form.Control>
|
|
||||||
{isFixedValueType ? (
|
|
||||||
<CurrencyInput
|
|
||||||
{...field}
|
|
||||||
min={0}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
onChange(value ? parseInt(value) : "")
|
|
||||||
}}
|
|
||||||
code={currencyCode}
|
|
||||||
symbol={
|
|
||||||
currencyCode
|
|
||||||
? getCurrencySymbol(currencyCode)
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
value={value}
|
|
||||||
disabled={!currencyCode}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<PercentageInput
|
|
||||||
key="amount"
|
|
||||||
className="text-right"
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
{...field}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => {
|
|
||||||
onChange(
|
|
||||||
e.target.value === ""
|
|
||||||
? null
|
|
||||||
: parseInt(e.target.value)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form.Control>
|
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isTypeStandard && (
|
|
||||||
<Form.Field
|
|
||||||
control={form.control}
|
|
||||||
name="application_method.allocation"
|
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>
|
<Form.Label>{t("promotions.fields.type")}</Form.Label>
|
||||||
{t("promotions.fields.allocation")}
|
|
||||||
</Form.Label>
|
|
||||||
|
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
className="flex gap-y-3"
|
className="flex gap-y-3"
|
||||||
@@ -639,21 +520,19 @@ export const CreatePromotionForm = () => {
|
|||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
>
|
>
|
||||||
<RadioGroup.ChoiceBox
|
<RadioGroup.ChoiceBox
|
||||||
value={"each"}
|
value={"standard"}
|
||||||
label={t("promotions.form.allocation.each.title")}
|
label={t("promotions.form.type.standard.title")}
|
||||||
description={t(
|
description={t(
|
||||||
"promotions.form.allocation.each.description"
|
"promotions.form.type.standard.description"
|
||||||
)}
|
)}
|
||||||
className={clx("basis-1/2")}
|
className={clx("basis-1/2")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RadioGroup.ChoiceBox
|
<RadioGroup.ChoiceBox
|
||||||
value={"across"}
|
value={"buyget"}
|
||||||
label={t(
|
label={t("promotions.form.type.buyget.title")}
|
||||||
"promotions.form.allocation.across.title"
|
|
||||||
)}
|
|
||||||
description={t(
|
description={t(
|
||||||
"promotions.form.allocation.across.description"
|
"promotions.form.type.buyget.description"
|
||||||
)}
|
)}
|
||||||
className={clx("basis-1/2")}
|
className={clx("basis-1/2")}
|
||||||
/>
|
/>
|
||||||
@@ -666,8 +545,141 @@ export const CreatePromotionForm = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isTypeStandard && watchAllocation === "each" && (
|
<Divider />
|
||||||
<div className="flex gap-y-4">
|
|
||||||
|
<RulesFormField form={form} ruleType={"rules"} />
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{!currentTemplate?.hiddenFields?.includes(
|
||||||
|
"application_method.type"
|
||||||
|
) && (
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="application_method.type"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("promotions.fields.value_type")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<RadioGroup
|
||||||
|
className="flex gap-y-3"
|
||||||
|
{...field}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"fixed"}
|
||||||
|
label={t(
|
||||||
|
"promotions.form.value_type.fixed.title"
|
||||||
|
)}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.value_type.fixed.description"
|
||||||
|
)}
|
||||||
|
className={clx("basis-1/2")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"percentage"}
|
||||||
|
label={t(
|
||||||
|
"promotions.form.value_type.percentage.title"
|
||||||
|
)}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.value_type.percentage.description"
|
||||||
|
)}
|
||||||
|
className={clx("basis-1/2")}
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-y-4 gap-x-2">
|
||||||
|
{!currentTemplate?.hiddenFields?.includes(
|
||||||
|
"application_method.value"
|
||||||
|
) && (
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="application_method.value"
|
||||||
|
render={({ field: { onChange, value, ...field } }) => {
|
||||||
|
const currencyCode =
|
||||||
|
form.getValues().application_method.currency_code
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item className="basis-1/2">
|
||||||
|
<Form.Label
|
||||||
|
tooltip={
|
||||||
|
currencyCode || !isFixedValueType
|
||||||
|
? undefined
|
||||||
|
: t("promotions.fields.amount.tooltip")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("promotions.form.value.title")}
|
||||||
|
</Form.Label>
|
||||||
|
|
||||||
|
<Form.Control>
|
||||||
|
{isFixedValueType ? (
|
||||||
|
<CurrencyInput
|
||||||
|
{...field}
|
||||||
|
min={0}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
onChange(value ? parseInt(value) : "")
|
||||||
|
}}
|
||||||
|
code={currencyCode}
|
||||||
|
symbol={
|
||||||
|
currencyCode
|
||||||
|
? getCurrencySymbol(currencyCode)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
value={value}
|
||||||
|
disabled={!currencyCode}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PercentageInput
|
||||||
|
key="amount"
|
||||||
|
className="text-right"
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
{...field}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange(
|
||||||
|
e.target.value === ""
|
||||||
|
? null
|
||||||
|
: parseInt(e.target.value)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Form.Control>
|
||||||
|
<Text
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
className="text-ui-fg-subtle"
|
||||||
|
>
|
||||||
|
<Trans
|
||||||
|
t={t}
|
||||||
|
i18nKey={
|
||||||
|
isFixedValueType
|
||||||
|
? "promotions.form.value_type.fixed.description"
|
||||||
|
: "promotions.form.value_type.percentage.description"
|
||||||
|
}
|
||||||
|
components={[<br key="break" />]}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isTypeStandard && watchAllocation === "each" && (
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="application_method.max_quantity"
|
name="application_method.max_quantity"
|
||||||
@@ -705,10 +717,58 @@ export const CreatePromotionForm = () => {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
<Divider />
|
{isTypeStandard &&
|
||||||
|
!currentTemplate?.hiddenFields?.includes(
|
||||||
|
"application_method.allocation"
|
||||||
|
) && (
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="application_method.allocation"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("promotions.fields.allocation")}
|
||||||
|
</Form.Label>
|
||||||
|
|
||||||
|
<Form.Control>
|
||||||
|
<RadioGroup
|
||||||
|
className="flex gap-y-3"
|
||||||
|
{...field}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"each"}
|
||||||
|
label={t(
|
||||||
|
"promotions.form.allocation.each.title"
|
||||||
|
)}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.allocation.each.description"
|
||||||
|
)}
|
||||||
|
className={clx("basis-1/2")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"across"}
|
||||||
|
label={t(
|
||||||
|
"promotions.form.allocation.across.title"
|
||||||
|
)}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.allocation.across.description"
|
||||||
|
)}
|
||||||
|
className={clx("basis-1/2")}
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{!isTypeStandard && (
|
{!isTypeStandard && (
|
||||||
<>
|
<>
|
||||||
@@ -717,16 +777,19 @@ export const CreatePromotionForm = () => {
|
|||||||
ruleType={"buy-rules"}
|
ruleType={"buy-rules"}
|
||||||
scope="application_method.buy_rules"
|
scope="application_method.buy_rules"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Divider />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<RulesFormField
|
{!isTargetTypeOrder && (
|
||||||
form={form}
|
<>
|
||||||
ruleType={"target-rules"}
|
<Divider />
|
||||||
scope="application_method.target_rules"
|
<RulesFormField
|
||||||
/>
|
form={form}
|
||||||
|
ruleType={"target-rules"}
|
||||||
|
scope="application_method.target_rules"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ProgressTabs.Content>
|
</ProgressTabs.Content>
|
||||||
|
|
||||||
<ProgressTabs.Content
|
<ProgressTabs.Content
|
||||||
|
|||||||
+3
-1
@@ -29,7 +29,7 @@ export const CreatePromotionSchema = z
|
|||||||
application_method: z.object({
|
application_method: z.object({
|
||||||
allocation: z.enum(["each", "across"]),
|
allocation: z.enum(["each", "across"]),
|
||||||
value: z.number().min(0),
|
value: z.number().min(0),
|
||||||
currency_code: z.string(),
|
currency_code: z.string().optional(),
|
||||||
max_quantity: z.number().optional().nullable(),
|
max_quantity: z.number().optional().nullable(),
|
||||||
target_rules: RuleSchema,
|
target_rules: RuleSchema,
|
||||||
buy_rules: RuleSchema,
|
buy_rules: RuleSchema,
|
||||||
@@ -54,3 +54,5 @@ export const CreatePromotionSchema = z
|
|||||||
message: `required field`,
|
message: `required field`,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export type CreatePromotionSchemaType = z.infer<typeof CreatePromotionSchema>
|
||||||
+12
-1
@@ -1,9 +1,16 @@
|
|||||||
|
const commonHiddenFields = [
|
||||||
|
"type",
|
||||||
|
"application_method.type",
|
||||||
|
"application_method.allocation",
|
||||||
|
]
|
||||||
|
|
||||||
export const templates = [
|
export const templates = [
|
||||||
{
|
{
|
||||||
id: "amount_off_products",
|
id: "amount_off_products",
|
||||||
type: "standard",
|
type: "standard",
|
||||||
title: "Amount off products",
|
title: "Amount off products",
|
||||||
description: "Discount specific products or collection of products",
|
description: "Discount specific products or collection of products",
|
||||||
|
hiddenFields: [...commonHiddenFields],
|
||||||
defaults: {
|
defaults: {
|
||||||
is_automatic: "false",
|
is_automatic: "false",
|
||||||
type: "standard",
|
type: "standard",
|
||||||
@@ -19,6 +26,7 @@ export const templates = [
|
|||||||
type: "standard",
|
type: "standard",
|
||||||
title: "Amount off order",
|
title: "Amount off order",
|
||||||
description: "Discounts the total order amount",
|
description: "Discounts the total order amount",
|
||||||
|
hiddenFields: [...commonHiddenFields],
|
||||||
defaults: {
|
defaults: {
|
||||||
is_automatic: "false",
|
is_automatic: "false",
|
||||||
type: "standard",
|
type: "standard",
|
||||||
@@ -34,6 +42,7 @@ export const templates = [
|
|||||||
type: "standard",
|
type: "standard",
|
||||||
title: "Percentage off product",
|
title: "Percentage off product",
|
||||||
description: "Discounts a percentage off selected products",
|
description: "Discounts a percentage off selected products",
|
||||||
|
hiddenFields: [...commonHiddenFields],
|
||||||
defaults: {
|
defaults: {
|
||||||
is_automatic: "false",
|
is_automatic: "false",
|
||||||
type: "standard",
|
type: "standard",
|
||||||
@@ -49,12 +58,13 @@ export const templates = [
|
|||||||
type: "standard",
|
type: "standard",
|
||||||
title: "Percentage off order",
|
title: "Percentage off order",
|
||||||
description: "Discounts a percentage of the total order amount",
|
description: "Discounts a percentage of the total order amount",
|
||||||
|
hiddenFields: [...commonHiddenFields],
|
||||||
defaults: {
|
defaults: {
|
||||||
is_automatic: "false",
|
is_automatic: "false",
|
||||||
type: "standard",
|
type: "standard",
|
||||||
application_method: {
|
application_method: {
|
||||||
allocation: "across",
|
allocation: "across",
|
||||||
target_type: "items",
|
target_type: "order",
|
||||||
type: "percentage",
|
type: "percentage",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -64,6 +74,7 @@ export const templates = [
|
|||||||
type: "buy_get",
|
type: "buy_get",
|
||||||
title: "Buy X Get Y",
|
title: "Buy X Get Y",
|
||||||
description: "Buy X product(s), get Y product(s)",
|
description: "Buy X product(s), get Y product(s)",
|
||||||
|
hiddenFields: [...commonHiddenFields, "application_method.value"],
|
||||||
defaults: {
|
defaults: {
|
||||||
is_automatic: "false",
|
is_automatic: "false",
|
||||||
type: "buyget",
|
type: "buyget",
|
||||||
|
|||||||
+1
-1
@@ -82,7 +82,7 @@ export const PromotionConditionsSection = ({
|
|||||||
className="h-[180px]"
|
className="h-[180px]"
|
||||||
title="No records yet."
|
title="No records yet."
|
||||||
message="Please check back later or add a target condition today"
|
message="Please check back later or add a target condition today"
|
||||||
action={{ to: "/promotions", label: "Add condition" }}
|
action={{ to: `${ruleType}/edit`, label: "Add condition" }}
|
||||||
buttonVariant="transparentIconLeft"
|
buttonVariant="transparentIconLeft"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ export interface RuleAttributeOptionsResponse {
|
|||||||
field_type: string
|
field_type: string
|
||||||
required: boolean
|
required: boolean
|
||||||
disguised: boolean
|
disguised: boolean
|
||||||
|
operators: {
|
||||||
|
id: string
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminRuleAttributeOptionsListResponse {
|
export interface AdminRuleAttributeOptionsListResponse {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export interface CreateApplicationMethodDTO {
|
|||||||
/**
|
/**
|
||||||
* Currency of the value to apply.
|
* Currency of the value to apply.
|
||||||
*/
|
*/
|
||||||
currency_code: string
|
currency_code?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The max quantity allowed in the cart for the associated promotion to be applied.
|
* The max quantity allowed in the cart for the associated promotion to be applied.
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ export const GET = async (
|
|||||||
})
|
})
|
||||||
|
|
||||||
const [promotion] = await remoteQuery(queryObject)
|
const [promotion] = await remoteQuery(queryObject)
|
||||||
const ruleAttributes = getRuleAttributesMap(
|
const ruleAttributes = getRuleAttributesMap({
|
||||||
promotion?.type || req.query.promotion_type
|
promotionType: promotion?.type || req.query.promotion_type,
|
||||||
)[ruleType]
|
applicationMethodType:
|
||||||
|
promotion?.application_method?.type || req.query.application_method_type,
|
||||||
|
})[ruleType]
|
||||||
const promotionRules: any[] = []
|
const promotionRules: any[] = []
|
||||||
|
|
||||||
if (dasherizedRuleType === RuleType.RULES) {
|
if (dasherizedRuleType === RuleType.RULES) {
|
||||||
@@ -48,7 +50,6 @@ export const GET = async (
|
|||||||
|
|
||||||
const transformedRules: AdminGetPromotionRulesRes = []
|
const transformedRules: AdminGetPromotionRulesRes = []
|
||||||
const disguisedRules = ruleAttributes.filter((attr) => !!attr.disguised)
|
const disguisedRules = ruleAttributes.filter((attr) => !!attr.disguised)
|
||||||
const requiredRules = ruleAttributes.filter((attr) => !!attr.required)
|
|
||||||
|
|
||||||
for (const disguisedRule of disguisedRules) {
|
for (const disguisedRule of disguisedRules) {
|
||||||
const getValues = () => {
|
const getValues = () => {
|
||||||
@@ -65,18 +66,22 @@ export const GET = async (
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
transformedRules.push({
|
const required = disguisedRule.required ?? true
|
||||||
id: undefined,
|
const applicationMethod = promotion?.application_method
|
||||||
attribute: disguisedRule.id,
|
const recordValue = applicationMethod?.[disguisedRule.id]
|
||||||
attribute_label: disguisedRule.label,
|
|
||||||
field_type: disguisedRule.field_type,
|
if (required || recordValue) {
|
||||||
hydrate: disguisedRule.hydrate || false,
|
transformedRules.push({
|
||||||
operator: RuleOperator.EQ,
|
...disguisedRule,
|
||||||
operator_label: operatorsMap[RuleOperator.EQ].label,
|
id: undefined,
|
||||||
values: getValues(),
|
attribute: disguisedRule.id,
|
||||||
disguised: true,
|
attribute_label: disguisedRule.label,
|
||||||
required: true,
|
operator: RuleOperator.EQ,
|
||||||
})
|
operator_label: operatorsMap[RuleOperator.EQ].label,
|
||||||
|
value: undefined,
|
||||||
|
values: getValues(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -125,36 +130,15 @@ export const GET = async (
|
|||||||
|
|
||||||
if (!currentRuleAttribute.hydrate) {
|
if (!currentRuleAttribute.hydrate) {
|
||||||
transformedRules.push({
|
transformedRules.push({
|
||||||
|
...currentRuleAttribute,
|
||||||
...promotionRule,
|
...promotionRule,
|
||||||
attribute_label: currentRuleAttribute.label,
|
attribute_label: currentRuleAttribute.label,
|
||||||
field_type: currentRuleAttribute.field_type,
|
|
||||||
operator_label:
|
operator_label:
|
||||||
operatorsMap[promotionRule.operator]?.label || promotionRule.operator,
|
operatorsMap[promotionRule.operator]?.label || promotionRule.operator,
|
||||||
disguised: false,
|
|
||||||
required: currentRuleAttribute.required || false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiredRules.length && !transformedRules.length) {
|
|
||||||
for (const requiredRule of requiredRules) {
|
|
||||||
transformedRules.push({
|
|
||||||
id: undefined,
|
|
||||||
attribute: requiredRule.value,
|
|
||||||
attribute_label: requiredRule.label,
|
|
||||||
operator: RuleOperator.EQ,
|
|
||||||
field_type: requiredRule.field_type,
|
|
||||||
operator_label: operatorsMap[RuleOperator.EQ].label,
|
|
||||||
values: [],
|
|
||||||
disguised: true,
|
|
||||||
required: true,
|
|
||||||
hydrate: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
rules: transformedRules,
|
rules: transformedRules,
|
||||||
})
|
})
|
||||||
|
|||||||
+4
-1
@@ -14,7 +14,10 @@ export const GET = async (
|
|||||||
validateRuleType(ruleType)
|
validateRuleType(ruleType)
|
||||||
|
|
||||||
const attributes =
|
const attributes =
|
||||||
getRuleAttributesMap(req.query.promotion_type as string)[ruleType] || []
|
getRuleAttributesMap({
|
||||||
|
promotionType: req.query.promotion_type as string,
|
||||||
|
applicationMethodType: req.query.application_method_type as string,
|
||||||
|
})[ruleType] || []
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
attributes,
|
attributes,
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import {
|
|
||||||
AuthenticatedMedusaRequest,
|
|
||||||
MedusaResponse,
|
|
||||||
} from "../../../../types/routing"
|
|
||||||
import { operatorsMap } from "../utils"
|
|
||||||
|
|
||||||
export const GET = async (
|
|
||||||
req: AuthenticatedMedusaRequest,
|
|
||||||
res: MedusaResponse
|
|
||||||
) => {
|
|
||||||
res.json({
|
|
||||||
operators: Object.values(operatorsMap),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
+14
-1
@@ -13,6 +13,13 @@ import {
|
|||||||
} from "../../../utils"
|
} from "../../../utils"
|
||||||
import { AdminGetPromotionRuleParamsType } from "../../../validators"
|
import { AdminGetPromotionRuleParamsType } from "../../../validators"
|
||||||
|
|
||||||
|
/*
|
||||||
|
This endpoint returns all the potential values for rules (promotion rules, target rules and buy rules)
|
||||||
|
given an attribute of a rule. The response for different rule_attributes are returned uniformly
|
||||||
|
as an array of labels and values.
|
||||||
|
Eg. If the rule_attribute requested is "currency_code" for "rules" rule type, we return currencies
|
||||||
|
from the currency module.
|
||||||
|
*/
|
||||||
export const GET = async (
|
export const GET = async (
|
||||||
req: AuthenticatedMedusaRequest<AdminGetPromotionRuleParamsType>,
|
req: AuthenticatedMedusaRequest<AdminGetPromotionRuleParamsType>,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
@@ -21,6 +28,7 @@ export const GET = async (
|
|||||||
rule_type: ruleType,
|
rule_type: ruleType,
|
||||||
rule_attribute_id: ruleAttributeId,
|
rule_attribute_id: ruleAttributeId,
|
||||||
promotion_type: promotionType,
|
promotion_type: promotionType,
|
||||||
|
application_method_type: applicationMethodType,
|
||||||
} = req.params
|
} = 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)
|
||||||
@@ -33,7 +41,12 @@ export const GET = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
validateRuleType(ruleType)
|
validateRuleType(ruleType)
|
||||||
validateRuleAttribute(promotionType, ruleType, ruleAttributeId)
|
validateRuleAttribute({
|
||||||
|
promotionType,
|
||||||
|
ruleType,
|
||||||
|
ruleAttributeId,
|
||||||
|
applicationMethodType,
|
||||||
|
})
|
||||||
|
|
||||||
const { rows } = await remoteQuery(
|
const { rows } = await remoteQuery(
|
||||||
remoteQueryObjectFromString({
|
remoteQueryObjectFromString({
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { PromotionType } from "@medusajs/utils"
|
import {
|
||||||
|
ApplicationMethodType,
|
||||||
|
PromotionType,
|
||||||
|
RuleOperator,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { operatorsMap } from "./operators-map"
|
||||||
|
|
||||||
export enum DisguisedRule {
|
export enum DisguisedRule {
|
||||||
APPLY_TO_QUANTITY = "apply_to_quantity",
|
APPLY_TO_QUANTITY = "apply_to_quantity",
|
||||||
@@ -7,21 +12,13 @@ export enum DisguisedRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ruleAttributes = [
|
const ruleAttributes = [
|
||||||
{
|
|
||||||
id: DisguisedRule.CURRENCY_CODE,
|
|
||||||
value: DisguisedRule.CURRENCY_CODE,
|
|
||||||
label: "Currency Code",
|
|
||||||
field_type: "select",
|
|
||||||
required: true,
|
|
||||||
disguised: true,
|
|
||||||
hydrate: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "customer_group",
|
id: "customer_group",
|
||||||
value: "customer.groups.id",
|
value: "customer.groups.id",
|
||||||
label: "Customer Group",
|
label: "Customer Group",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "region",
|
id: "region",
|
||||||
@@ -29,6 +26,7 @@ const ruleAttributes = [
|
|||||||
label: "Region",
|
label: "Region",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "country",
|
id: "country",
|
||||||
@@ -36,6 +34,7 @@ const ruleAttributes = [
|
|||||||
label: "Country",
|
label: "Country",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "sales_channel",
|
id: "sales_channel",
|
||||||
@@ -43,6 +42,7 @@ const ruleAttributes = [
|
|||||||
label: "Sales Channel",
|
label: "Sales Channel",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@ const commonAttributes = [
|
|||||||
label: "Product",
|
label: "Product",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "product_category",
|
id: "product_category",
|
||||||
@@ -60,6 +61,7 @@ const commonAttributes = [
|
|||||||
label: "Product Category",
|
label: "Product Category",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "product_collection",
|
id: "product_collection",
|
||||||
@@ -67,6 +69,7 @@ const commonAttributes = [
|
|||||||
label: "Product Collection",
|
label: "Product Collection",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "product_type",
|
id: "product_type",
|
||||||
@@ -74,6 +77,7 @@ const commonAttributes = [
|
|||||||
label: "Product Type",
|
label: "Product Type",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "product_tag",
|
id: "product_tag",
|
||||||
@@ -81,9 +85,21 @@ const commonAttributes = [
|
|||||||
label: "Product Tag",
|
label: "Product Tag",
|
||||||
required: false,
|
required: false,
|
||||||
field_type: "multiselect",
|
field_type: "multiselect",
|
||||||
|
operators: Object.values(operatorsMap),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const currencyRule = {
|
||||||
|
id: DisguisedRule.CURRENCY_CODE,
|
||||||
|
value: DisguisedRule.CURRENCY_CODE,
|
||||||
|
label: "Currency Code",
|
||||||
|
field_type: "select",
|
||||||
|
required: true,
|
||||||
|
disguised: true,
|
||||||
|
hydrate: true,
|
||||||
|
operators: [operatorsMap[RuleOperator.EQ]],
|
||||||
|
}
|
||||||
|
|
||||||
const buyGetBuyRules = [
|
const buyGetBuyRules = [
|
||||||
{
|
{
|
||||||
id: DisguisedRule.BUY_RULES_MIN_QUANTITY,
|
id: DisguisedRule.BUY_RULES_MIN_QUANTITY,
|
||||||
@@ -92,6 +108,7 @@ const buyGetBuyRules = [
|
|||||||
field_type: "number",
|
field_type: "number",
|
||||||
required: true,
|
required: true,
|
||||||
disguised: true,
|
disguised: true,
|
||||||
|
operators: [operatorsMap[RuleOperator.EQ]],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -103,16 +120,29 @@ const buyGetTargetRules = [
|
|||||||
field_type: "number",
|
field_type: "number",
|
||||||
required: true,
|
required: true,
|
||||||
disguised: true,
|
disguised: true,
|
||||||
|
operators: [operatorsMap[RuleOperator.EQ]],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const getRuleAttributesMap = (promotionType?: string) => {
|
export const getRuleAttributesMap = ({
|
||||||
|
promotionType,
|
||||||
|
applicationMethodType,
|
||||||
|
}: {
|
||||||
|
promotionType?: string
|
||||||
|
applicationMethodType?: string
|
||||||
|
}) => {
|
||||||
const map = {
|
const map = {
|
||||||
rules: [...ruleAttributes],
|
rules: [...ruleAttributes],
|
||||||
"target-rules": [...commonAttributes],
|
"target-rules": [...commonAttributes],
|
||||||
"buy-rules": [...commonAttributes],
|
"buy-rules": [...commonAttributes],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (applicationMethodType === ApplicationMethodType.FIXED) {
|
||||||
|
map["rules"].push({ ...currencyRule })
|
||||||
|
} else {
|
||||||
|
map["rules"].push({ ...currencyRule, required: false })
|
||||||
|
}
|
||||||
|
|
||||||
if (promotionType === PromotionType.BUYGET) {
|
if (promotionType === PromotionType.BUYGET) {
|
||||||
map["buy-rules"].push(...buyGetBuyRules)
|
map["buy-rules"].push(...buyGetBuyRules)
|
||||||
map["target-rules"].push(...buyGetTargetRules)
|
map["target-rules"].push(...buyGetTargetRules)
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import { MedusaError } from "@medusajs/utils"
|
import { MedusaError } from "@medusajs/utils"
|
||||||
import { getRuleAttributesMap } from "./rule-attributes-map"
|
import { getRuleAttributesMap } from "./rule-attributes-map"
|
||||||
|
|
||||||
export function validateRuleAttribute(
|
export function validateRuleAttribute(attributes: {
|
||||||
promotionType: string | undefined,
|
promotionType: string | undefined
|
||||||
ruleType: string,
|
ruleType: string
|
||||||
ruleAttributeId: string
|
ruleAttributeId: string
|
||||||
) {
|
applicationMethodType?: string
|
||||||
const ruleAttributes = getRuleAttributesMap(promotionType)[ruleType] || []
|
}) {
|
||||||
|
const { promotionType, ruleType, ruleAttributeId, applicationMethodType } =
|
||||||
|
attributes
|
||||||
|
|
||||||
|
const ruleAttributes =
|
||||||
|
getRuleAttributesMap({
|
||||||
|
promotionType,
|
||||||
|
applicationMethodType,
|
||||||
|
})[ruleType] || []
|
||||||
|
|
||||||
const ruleAttribute = ruleAttributes.find((obj) => obj.id === ruleAttributeId)
|
const ruleAttribute = ruleAttributes.find((obj) => obj.id === ruleAttributeId)
|
||||||
|
|
||||||
if (!ruleAttribute) {
|
if (!ruleAttribute) {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export type AdminGetPromotionRuleParamsType = z.infer<
|
|||||||
>
|
>
|
||||||
export const AdminGetPromotionRuleParams = z.object({
|
export const AdminGetPromotionRuleParams = z.object({
|
||||||
promotion_type: z.string().optional(),
|
promotion_type: z.string().optional(),
|
||||||
|
application_method_type: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type AdminGetPromotionRuleTypeParamsType = z.infer<
|
export type AdminGetPromotionRuleTypeParamsType = z.infer<
|
||||||
@@ -57,6 +58,7 @@ export type AdminGetPromotionRuleTypeParamsType = z.infer<
|
|||||||
export const AdminGetPromotionRuleTypeParams = createSelectParams().merge(
|
export const AdminGetPromotionRuleTypeParams = createSelectParams().merge(
|
||||||
z.object({
|
z.object({
|
||||||
promotion_type: z.string().optional(),
|
promotion_type: z.string().optional(),
|
||||||
|
application_method_type: z.string().optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,7 +107,7 @@ export const AdminCreateApplicationMethod = z
|
|||||||
.object({
|
.object({
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
value: z.number(),
|
value: z.number(),
|
||||||
currency_code: z.string(),
|
currency_code: z.string().optional().nullable(),
|
||||||
max_quantity: z.number().optional().nullable(),
|
max_quantity: z.number().optional().nullable(),
|
||||||
type: z.nativeEnum(ApplicationMethodType),
|
type: z.nativeEnum(ApplicationMethodType),
|
||||||
target_type: z.nativeEnum(ApplicationMethodTargetType),
|
target_type: z.nativeEnum(ApplicationMethodTargetType),
|
||||||
@@ -125,7 +127,7 @@ export const AdminUpdateApplicationMethod = z
|
|||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
value: z.number().optional(),
|
value: z.number().optional(),
|
||||||
max_quantity: z.number().optional().nullable(),
|
max_quantity: z.number().optional().nullable(),
|
||||||
currency_code: z.string().optional(),
|
currency_code: z.string().optional().nullable(),
|
||||||
type: z.nativeEnum(ApplicationMethodType).optional(),
|
type: z.nativeEnum(ApplicationMethodType).optional(),
|
||||||
target_type: z.nativeEnum(ApplicationMethodTargetType).optional(),
|
target_type: z.nativeEnum(ApplicationMethodTargetType).optional(),
|
||||||
allocation: z.nativeEnum(ApplicationMethodAllocation).optional(),
|
allocation: z.nativeEnum(ApplicationMethodAllocation).optional(),
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Migration } from "@mikro-orm/migrations"
|
||||||
|
|
||||||
|
export class Migration20240617102917 extends Migration {
|
||||||
|
async up(): Promise<void> {
|
||||||
|
this.addSql(
|
||||||
|
'alter table "promotion_application_method" alter column "currency_code" type text using ("currency_code"::text);'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table "promotion_application_method" alter column "currency_code" drop not null;'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(): Promise<void> {
|
||||||
|
this.addSql(
|
||||||
|
'alter table "promotion_application_method" alter column "currency_code" type text using ("currency_code"::text);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table "promotion_application_method" alter column "currency_code" set not null;'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ export interface CreateApplicationMethodDTO {
|
|||||||
target_type: ApplicationMethodTargetTypeValues
|
target_type: ApplicationMethodTargetTypeValues
|
||||||
allocation?: ApplicationMethodAllocationValues
|
allocation?: ApplicationMethodAllocationValues
|
||||||
value?: BigNumberInput
|
value?: BigNumberInput
|
||||||
currency_code: string
|
currency_code?: string | null
|
||||||
promotion: Promotion | string | PromotionDTO
|
promotion: Promotion | string | PromotionDTO
|
||||||
max_quantity?: BigNumberInput | null
|
max_quantity?: BigNumberInput | null
|
||||||
buy_rules_min_quantity?: BigNumberInput | null
|
buy_rules_min_quantity?: BigNumberInput | null
|
||||||
@@ -26,7 +26,7 @@ export interface UpdateApplicationMethodDTO {
|
|||||||
target_type?: ApplicationMethodTargetTypeValues
|
target_type?: ApplicationMethodTargetTypeValues
|
||||||
allocation?: ApplicationMethodAllocationValues
|
allocation?: ApplicationMethodAllocationValues
|
||||||
value?: BigNumberInput
|
value?: BigNumberInput
|
||||||
currency_code?: string
|
currency_code?: string | null
|
||||||
promotion?: Promotion | string | PromotionDTO
|
promotion?: Promotion | string | PromotionDTO
|
||||||
max_quantity?: BigNumberInput | null
|
max_quantity?: BigNumberInput | null
|
||||||
buy_rules_min_quantity?: BigNumberInput | null
|
buy_rules_min_quantity?: BigNumberInput | null
|
||||||
|
|||||||
Reference in new issue
Block a user