feat(dashboard,core,modules): free shipping promotion in dashboard (#13263)
* feat(dashboard,core,modules): free shipping promotion in dashboard * self-review * adapt for edit to work * changeset * integration tests * across for each * remove only from tests * remove console log * revert to across * update wording for shipping promotions * modify changeset * suggestion frane * fix i18n schema
This commit is contained in:
1 parent
92e3b2b432
commit
486621383a
22 files changed
+215
-72
No files matched your search
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/link-modules": patch
|
||||||
|
"@medusajs/promotion": patch
|
||||||
|
"@medusajs/dashboard": patch
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/js-sdk": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(dashboard,core-flows,js-sdk,link-modules,promotion): free shipping promotion in dashboard
|
||||||
@@ -21,10 +21,15 @@ export const promotionsQueryKeys = {
|
|||||||
ruleType: string,
|
ruleType: string,
|
||||||
query?: HttpTypes.AdminGetPromotionRuleParams
|
query?: HttpTypes.AdminGetPromotionRuleParams
|
||||||
) => [PROMOTIONS_QUERY_KEY, id, ruleType, query],
|
) => [PROMOTIONS_QUERY_KEY, id, ruleType, query],
|
||||||
listRuleAttributes: (ruleType: string, promotionType?: string) => [
|
listRuleAttributes: (
|
||||||
|
ruleType: string,
|
||||||
|
promotionType?: string,
|
||||||
|
applicationMethodTargetType?: string
|
||||||
|
) => [
|
||||||
PROMOTIONS_QUERY_KEY,
|
PROMOTIONS_QUERY_KEY,
|
||||||
ruleType,
|
ruleType,
|
||||||
promotionType,
|
promotionType,
|
||||||
|
applicationMethodTargetType,
|
||||||
],
|
],
|
||||||
listRuleValues: (
|
listRuleValues: (
|
||||||
ruleType: string,
|
ruleType: string,
|
||||||
@@ -101,6 +106,7 @@ export const usePromotions = (
|
|||||||
export const usePromotionRuleAttributes = (
|
export const usePromotionRuleAttributes = (
|
||||||
ruleType: string,
|
ruleType: string,
|
||||||
promotionType?: string,
|
promotionType?: string,
|
||||||
|
applicationMethodTargetType?: string,
|
||||||
options?: Omit<
|
options?: Omit<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
HttpTypes.AdminRuleAttributeOptionsListResponse,
|
HttpTypes.AdminRuleAttributeOptionsListResponse,
|
||||||
@@ -112,9 +118,17 @@ export const usePromotionRuleAttributes = (
|
|||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
const { data, ...rest } = useQuery({
|
const { data, ...rest } = useQuery({
|
||||||
queryKey: promotionsQueryKeys.listRuleAttributes(ruleType, promotionType),
|
queryKey: promotionsQueryKeys.listRuleAttributes(
|
||||||
|
ruleType,
|
||||||
|
promotionType,
|
||||||
|
applicationMethodTargetType
|
||||||
|
),
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
sdk.admin.promotion.listRuleAttributes(ruleType, promotionType),
|
sdk.admin.promotion.listRuleAttributes(
|
||||||
|
ruleType,
|
||||||
|
promotionType,
|
||||||
|
applicationMethodTargetType
|
||||||
|
),
|
||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -7514,6 +7514,9 @@
|
|||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"target-rules": {
|
"target-rules": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"order": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": {
|
"title": {
|
||||||
@@ -7526,6 +7529,36 @@
|
|||||||
"required": ["title", "description"],
|
"required": ["title", "description"],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"shipping_methods": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title", "description"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title", "description"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["order", "shipping_methods", "items"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"buy-rules": {
|
"buy-rules": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -2010,9 +2010,19 @@
|
|||||||
"description": "Which customer is allowed to use the promotion code? Promotion code can be used by all customers if left untouched."
|
"description": "Which customer is allowed to use the promotion code? Promotion code can be used by all customers if left untouched."
|
||||||
},
|
},
|
||||||
"target-rules": {
|
"target-rules": {
|
||||||
|
"order": {
|
||||||
"title": "What items will the promotion be applied to?",
|
"title": "What items will the promotion be applied to?",
|
||||||
"description": "The promotion will be applied to items that match the following conditions."
|
"description": "The promotion will be applied to items that match the following conditions."
|
||||||
},
|
},
|
||||||
|
"shipping_methods": {
|
||||||
|
"title": "What shipping methods will the promotion be applied to?",
|
||||||
|
"description": "The promotion will be applied to shipping methods that match the following conditions."
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"title": "What items will the promotion be applied to?",
|
||||||
|
"description": "The promotion will be applied to items that match the following conditions."
|
||||||
|
}
|
||||||
|
},
|
||||||
"buy-rules": {
|
"buy-rules": {
|
||||||
"title": "What needs to be in the cart to unlock the promotion?",
|
"title": "What needs to be in the cart to unlock the promotion?",
|
||||||
"description": "If these conditions match, we enable the promotion on the target items."
|
"description": "If these conditions match, we enable the promotion on the target items."
|
||||||
|
|||||||
+7
-1
@@ -28,7 +28,13 @@ export const EditRulesForm = ({
|
|||||||
const [rulesToRemove, setRulesToRemove] = useState([])
|
const [rulesToRemove, setRulesToRemove] = useState([])
|
||||||
|
|
||||||
const form = useForm<EditRulesType>({
|
const form = useForm<EditRulesType>({
|
||||||
defaultValues: { rules: [], type: promotion.type },
|
defaultValues: {
|
||||||
|
rules: [],
|
||||||
|
type: promotion.type,
|
||||||
|
application_method: {
|
||||||
|
target_type: promotion.application_method?.target_type,
|
||||||
|
},
|
||||||
|
},
|
||||||
resolver: zodResolver(EditRules),
|
resolver: zodResolver(EditRules),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+3
@@ -24,6 +24,9 @@ export const EditRules = z.object({
|
|||||||
field_type: z.string().optional(),
|
field_type: z.string().optional(),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
application_method: z.object({
|
||||||
|
target_type: z.enum(["order", "shipping_methods", "items"]),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type EditRulesType = z.infer<typeof EditRules>
|
export type EditRulesType = z.infer<typeof EditRules>
|
||||||
+10
-3
@@ -1,9 +1,13 @@
|
|||||||
import { HttpTypes } from "@medusajs/types"
|
import {
|
||||||
|
ApplicationMethodTargetTypeValues,
|
||||||
|
HttpTypes,
|
||||||
|
RuleTypeValues,
|
||||||
|
} from "@medusajs/types"
|
||||||
import { Input } from "@medusajs/ui"
|
import { Input } from "@medusajs/ui"
|
||||||
import { useWatch } from "react-hook-form"
|
import { useWatch } from "react-hook-form"
|
||||||
import { Form } from "../../../../../../components/common/form"
|
import { Form } from "../../../../../../components/common/form"
|
||||||
import { Combobox } from "../../../../../../components/inputs/combobox"
|
import { Combobox } from "../../../../../../components/inputs/combobox"
|
||||||
import { useStore } from "../../../../../../hooks/api/store"
|
import { useStore } from "../../../../../../hooks/api"
|
||||||
import { useComboboxData } from "../../../../../../hooks/use-combobox-data"
|
import { useComboboxData } from "../../../../../../hooks/use-combobox-data"
|
||||||
import { sdk } from "../../../../../../lib/client"
|
import { sdk } from "../../../../../../lib/client"
|
||||||
|
|
||||||
@@ -18,7 +22,8 @@ type RuleValueFormFieldType = {
|
|||||||
operator: string
|
operator: string
|
||||||
fieldRule: any
|
fieldRule: any
|
||||||
attributes: HttpTypes.AdminRuleAttributeOption[]
|
attributes: HttpTypes.AdminRuleAttributeOption[]
|
||||||
ruleType: "rules" | "target-rules" | "buy-rules"
|
ruleType: RuleTypeValues
|
||||||
|
applicationMethodTargetType: ApplicationMethodTargetTypeValues | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildFilters = (attribute?: string, store?: HttpTypes.AdminStore) => {
|
const buildFilters = (attribute?: string, store?: HttpTypes.AdminStore) => {
|
||||||
@@ -44,6 +49,7 @@ export const RuleValueFormField = ({
|
|||||||
fieldRule,
|
fieldRule,
|
||||||
attributes,
|
attributes,
|
||||||
ruleType,
|
ruleType,
|
||||||
|
applicationMethodTargetType,
|
||||||
}: RuleValueFormFieldType) => {
|
}: RuleValueFormFieldType) => {
|
||||||
const attribute = attributes?.find(
|
const attribute = attributes?.find(
|
||||||
(attr) => attr.value === fieldRule.attribute
|
(attr) => attr.value === fieldRule.attribute
|
||||||
@@ -59,6 +65,7 @@ export const RuleValueFormField = ({
|
|||||||
{
|
{
|
||||||
...params,
|
...params,
|
||||||
...buildFilters(attribute?.id, store!),
|
...buildFilters(attribute?.id, store!),
|
||||||
|
application_method_target_type: applicationMethodTargetType,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
+23
-3
@@ -41,7 +41,11 @@ export const RulesFormField = ({
|
|||||||
}: 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,
|
||||||
|
formData.application_method?.target_type
|
||||||
|
)
|
||||||
|
|
||||||
const { fields, append, remove, update, replace } = useFieldArray({
|
const { fields, append, remove, update, replace } = useFieldArray({
|
||||||
control: form.control,
|
control: form.control,
|
||||||
@@ -61,10 +65,17 @@ export const RulesFormField = ({
|
|||||||
defaultValue: promotion?.application_method?.type,
|
defaultValue: promotion?.application_method?.type,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const applicationMethodTargetType = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: "application_method.target_type",
|
||||||
|
defaultValue: promotion?.application_method?.target_type,
|
||||||
|
})
|
||||||
|
|
||||||
const query: Record<string, string> = promotionType
|
const query: Record<string, string> = promotionType
|
||||||
? {
|
? {
|
||||||
promotion_type: promotionType,
|
promotion_type: promotionType,
|
||||||
application_method_type: applicationMethodType,
|
application_method_type: applicationMethodType,
|
||||||
|
application_method_target_type: applicationMethodTargetType,
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
@@ -121,11 +132,19 @@ export const RulesFormField = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Heading level="h2" className="mb-2">
|
<Heading level="h2" className="mb-2">
|
||||||
{t(`promotions.fields.conditions.${ruleType}.title`)}
|
{t(
|
||||||
|
ruleType === "target-rules"
|
||||||
|
? `promotions.fields.conditions.${ruleType}.${applicationMethodTargetType}.title`
|
||||||
|
: `promotions.fields.conditions.${ruleType}.title`
|
||||||
|
)}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<Text className="text-ui-fg-subtle txt-small mb-6">
|
<Text className="text-ui-fg-subtle txt-small mb-6">
|
||||||
{t(`promotions.fields.conditions.${ruleType}.description`)}
|
{t(
|
||||||
|
ruleType === "target-rules"
|
||||||
|
? `promotions.fields.conditions.${ruleType}.${applicationMethodTargetType}.description`
|
||||||
|
: `promotions.fields.conditions.${ruleType}.description`
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{fields.map((fieldRule, index) => {
|
{fields.map((fieldRule, index) => {
|
||||||
@@ -307,6 +326,7 @@ export const RulesFormField = ({
|
|||||||
fieldRule={fieldRule}
|
fieldRule={fieldRule}
|
||||||
attributes={attributes}
|
attributes={attributes}
|
||||||
ruleType={ruleType}
|
ruleType={ruleType}
|
||||||
|
applicationMethodTargetType={applicationMethodTargetType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+7
-4
@@ -678,11 +678,11 @@ export const CreatePromotionForm = () => {
|
|||||||
|
|
||||||
<RulesFormField form={form} ruleType={"rules"} />
|
<RulesFormField form={form} ruleType={"rules"} />
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{!currentTemplate?.hiddenFields?.includes(
|
{!currentTemplate?.hiddenFields?.includes(
|
||||||
"application_method.type"
|
"application_method.type"
|
||||||
) && (
|
) && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="application_method.type"
|
name="application_method.type"
|
||||||
@@ -726,12 +726,14 @@ export const CreatePromotionForm = () => {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex gap-x-2 gap-y-4">
|
|
||||||
{!currentTemplate?.hiddenFields?.includes(
|
{!currentTemplate?.hiddenFields?.includes(
|
||||||
"application_method.value"
|
"application_method.value"
|
||||||
) && (
|
) && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="application_method.value"
|
name="application_method.value"
|
||||||
@@ -806,6 +808,7 @@ export const CreatePromotionForm = () => {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isTypeStandard && watchAllocation === "each" && (
|
{isTypeStandard && watchAllocation === "each" && (
|
||||||
@@ -847,7 +850,6 @@ export const CreatePromotionForm = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{isTypeStandard &&
|
{isTypeStandard &&
|
||||||
!currentTemplate?.hiddenFields?.includes(
|
!currentTemplate?.hiddenFields?.includes(
|
||||||
@@ -901,6 +903,7 @@ export const CreatePromotionForm = () => {
|
|||||||
|
|
||||||
{!isTypeStandard && (
|
{!isTypeStandard && (
|
||||||
<>
|
<>
|
||||||
|
<Divider />
|
||||||
<RulesFormField
|
<RulesFormField
|
||||||
form={form}
|
form={form}
|
||||||
ruleType={"buy-rules"}
|
ruleType={"buy-rules"}
|
||||||
|
|||||||
+23
@@ -22,6 +22,12 @@ const buyGetHiddenFields = [
|
|||||||
"is_tax_inclusive",
|
"is_tax_inclusive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const freeShippingHiddenFields = [
|
||||||
|
...commonHiddenFields,
|
||||||
|
"application_method.value",
|
||||||
|
"is_tax_inclusive",
|
||||||
|
]
|
||||||
|
|
||||||
export const templates = [
|
export const templates = [
|
||||||
{
|
{
|
||||||
id: "amount_off_products",
|
id: "amount_off_products",
|
||||||
@@ -104,4 +110,21 @@ export const templates = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "shipping_discount",
|
||||||
|
type: "standard",
|
||||||
|
title: "Free shipping",
|
||||||
|
description: "Applies a 100% discount to shipping fees",
|
||||||
|
hiddenFields: freeShippingHiddenFields,
|
||||||
|
defaults: {
|
||||||
|
is_automatic: "false",
|
||||||
|
type: "standard",
|
||||||
|
application_method: {
|
||||||
|
allocation: "across",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
type: "percentage",
|
||||||
|
value: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
+8
-2
@@ -1,5 +1,5 @@
|
|||||||
import { PencilSquare } from "@medusajs/icons"
|
import { PencilSquare } from "@medusajs/icons"
|
||||||
import { HttpTypes, PromotionRuleTypes } from "@medusajs/types"
|
import { ApplicationMethodTargetTypeValues, HttpTypes, PromotionRuleTypes, } from "@medusajs/types"
|
||||||
import { Badge, Container, Heading } from "@medusajs/ui"
|
import { Badge, Container, Heading } from "@medusajs/ui"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
@@ -44,11 +44,13 @@ function RuleBlock({ rule }: RuleProps) {
|
|||||||
type PromotionConditionsSectionProps = {
|
type PromotionConditionsSectionProps = {
|
||||||
rules: HttpTypes.AdminPromotionRule[]
|
rules: HttpTypes.AdminPromotionRule[]
|
||||||
ruleType: PromotionRuleTypes
|
ruleType: PromotionRuleTypes
|
||||||
|
applicationMethodTargetType: ApplicationMethodTargetTypeValues
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PromotionConditionsSection = ({
|
export const PromotionConditionsSection = ({
|
||||||
rules,
|
rules,
|
||||||
ruleType,
|
ruleType,
|
||||||
|
applicationMethodTargetType,
|
||||||
}: PromotionConditionsSectionProps) => {
|
}: PromotionConditionsSectionProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@@ -57,7 +59,11 @@ export const PromotionConditionsSection = ({
|
|||||||
<div className="flex items-center justify-between px-6 py-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Heading>
|
<Heading>
|
||||||
{t(`promotions.fields.conditions.${ruleType}.title`)}
|
{t(
|
||||||
|
ruleType === "target-rules"
|
||||||
|
? `promotions.fields.conditions.${ruleType}.${applicationMethodTargetType}.title`
|
||||||
|
: `promotions.fields.conditions.${ruleType}.title`
|
||||||
|
)}
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -52,11 +52,15 @@ export const PromotionDetail = () => {
|
|||||||
<PromotionConditionsSection
|
<PromotionConditionsSection
|
||||||
rules={targetRules || []}
|
rules={targetRules || []}
|
||||||
ruleType={"target-rules"}
|
ruleType={"target-rules"}
|
||||||
|
applicationMethodTargetType={
|
||||||
|
promotion.application_method.target_type || "items"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{promotion.type === "buyget" && (
|
{promotion.type === "buyget" && (
|
||||||
<PromotionConditionsSection
|
<PromotionConditionsSection
|
||||||
rules={buyRules || []}
|
rules={buyRules || []}
|
||||||
ruleType={"buy-rules"}
|
ruleType={"buy-rules"}
|
||||||
|
applicationMethodTargetType={"items"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</TwoColumnPage.Main>
|
</TwoColumnPage.Main>
|
||||||
|
|||||||
+20
-14
@@ -1,13 +1,6 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { AdminPromotion } from "@medusajs/types"
|
import { AdminPromotion } from "@medusajs/types"
|
||||||
import {
|
import { Button, CurrencyInput, Input, RadioGroup, Text } from "@medusajs/ui"
|
||||||
Button,
|
|
||||||
CurrencyInput,
|
|
||||||
Input,
|
|
||||||
RadioGroup,
|
|
||||||
Switch,
|
|
||||||
Text,
|
|
||||||
} from "@medusajs/ui"
|
|
||||||
import { 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 { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
@@ -33,6 +26,7 @@ const EditPromotionSchema = zod.object({
|
|||||||
value_type: zod.enum(["fixed", "percentage"]),
|
value_type: zod.enum(["fixed", "percentage"]),
|
||||||
value: zod.number(),
|
value: zod.number(),
|
||||||
allocation: zod.enum(["each", "across"]),
|
allocation: zod.enum(["each", "across"]),
|
||||||
|
target_type: zod.enum(["order", "shipping_methods", "items"]),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const EditPromotionDetailsForm = ({
|
export const EditPromotionDetailsForm = ({
|
||||||
@@ -50,6 +44,7 @@ export const EditPromotionDetailsForm = ({
|
|||||||
value: promotion.application_method!.value,
|
value: promotion.application_method!.value,
|
||||||
allocation: promotion.application_method!.allocation,
|
allocation: promotion.application_method!.allocation,
|
||||||
value_type: promotion.application_method!.type,
|
value_type: promotion.application_method!.type,
|
||||||
|
target_type: promotion.application_method!.target_type,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(EditPromotionSchema),
|
resolver: zodResolver(EditPromotionSchema),
|
||||||
})
|
})
|
||||||
@@ -223,13 +218,18 @@ export const EditPromotionDetailsForm = ({
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{promotion.application_method?.target_type !==
|
||||||
|
"shipping_methods" && (
|
||||||
|
<>
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="value_type"
|
name="value_type"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("promotions.fields.value_type")}</Form.Label>
|
<Form.Label>
|
||||||
|
{t("promotions.fields.value_type")}
|
||||||
|
</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
className="flex-col gap-y-3"
|
className="flex-col gap-y-3"
|
||||||
@@ -238,7 +238,9 @@ export const EditPromotionDetailsForm = ({
|
|||||||
>
|
>
|
||||||
<RadioGroup.ChoiceBox
|
<RadioGroup.ChoiceBox
|
||||||
value={"fixed"}
|
value={"fixed"}
|
||||||
label={t("promotions.form.value_type.fixed.title")}
|
label={t(
|
||||||
|
"promotions.form.value_type.fixed.title"
|
||||||
|
)}
|
||||||
description={t(
|
description={t(
|
||||||
"promotions.form.value_type.fixed.description"
|
"promotions.form.value_type.fixed.description"
|
||||||
)}
|
)}
|
||||||
@@ -260,7 +262,6 @@ export const EditPromotionDetailsForm = ({
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="value"
|
name="value"
|
||||||
@@ -309,14 +310,15 @@ export const EditPromotionDetailsForm = ({
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="allocation"
|
name="allocation"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("promotions.fields.allocation")}</Form.Label>
|
<Form.Label>
|
||||||
|
{t("promotions.fields.allocation")}
|
||||||
|
</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
className="flex-col gap-y-3"
|
className="flex-col gap-y-3"
|
||||||
@@ -333,7 +335,9 @@ export const EditPromotionDetailsForm = ({
|
|||||||
|
|
||||||
<RadioGroup.ChoiceBox
|
<RadioGroup.ChoiceBox
|
||||||
value={"across"}
|
value={"across"}
|
||||||
label={t("promotions.form.allocation.across.title")}
|
label={t(
|
||||||
|
"promotions.form.allocation.across.title"
|
||||||
|
)}
|
||||||
description={t(
|
description={t(
|
||||||
"promotions.form.allocation.across.description"
|
"promotions.form.allocation.across.description"
|
||||||
)}
|
)}
|
||||||
@@ -345,6 +349,8 @@ export const EditPromotionDetailsForm = ({
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</RouteDrawer.Body>
|
</RouteDrawer.Body>
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const cartFieldsForRefreshSteps = [
|
|||||||
"shipping_methods.*",
|
"shipping_methods.*",
|
||||||
"shipping_methods.adjustments.*",
|
"shipping_methods.adjustments.*",
|
||||||
"shipping_methods.tax_lines.*",
|
"shipping_methods.tax_lines.*",
|
||||||
|
"shipping_methods.shipping_option.shipping_option_type_id",
|
||||||
"customer.*",
|
"customer.*",
|
||||||
"customer.groups.*",
|
"customer.groups.*",
|
||||||
"promotions.id",
|
"promotions.id",
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ export class Promotion {
|
|||||||
*
|
*
|
||||||
* @param ruleType - The type of rules to retrieve the attributes for. Can be `rules`, `buy-rules`, or `target-rules`.
|
* @param ruleType - The type of rules to retrieve the attributes for. Can be `rules`, `buy-rules`, or `target-rules`.
|
||||||
* @param promotionType - The type of promotion to retrieve the attributes for. It can be `standard` or `buyget`.
|
* @param promotionType - The type of promotion to retrieve the attributes for. It can be `standard` or `buyget`.
|
||||||
|
* @param applicationMethodTargetType - The type of application method to retrieve the attributes for. It can be `order`, `items` or `shipping_methods`.
|
||||||
* @param headers - Headers to pass in the request.
|
* @param headers - Headers to pass in the request.
|
||||||
* @returns The list of rule attributes.
|
* @returns The list of rule attributes.
|
||||||
*
|
*
|
||||||
@@ -411,6 +412,7 @@ export class Promotion {
|
|||||||
async listRuleAttributes(
|
async listRuleAttributes(
|
||||||
ruleType: string,
|
ruleType: string,
|
||||||
promotionType?: string,
|
promotionType?: string,
|
||||||
|
applicationMethodTargetType?: string,
|
||||||
headers?: ClientHeaders
|
headers?: ClientHeaders
|
||||||
) {
|
) {
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
@@ -418,7 +420,10 @@ export class Promotion {
|
|||||||
`/admin/promotions/rule-attribute-options/${ruleType}`,
|
`/admin/promotions/rule-attribute-options/${ruleType}`,
|
||||||
{
|
{
|
||||||
headers,
|
headers,
|
||||||
query: { promotion_type: promotionType },
|
query: {
|
||||||
|
promotion_type: promotionType,
|
||||||
|
application_method_target_type: applicationMethodTargetType,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BaseFilterable, OperatorMap } from "../../../dal"
|
import { BaseFilterable, OperatorMap } from "../../../dal"
|
||||||
import {
|
import {
|
||||||
|
ApplicationMethodTargetTypeValues,
|
||||||
ApplicationMethodTypeValues,
|
ApplicationMethodTypeValues,
|
||||||
PromotionTypeValues,
|
PromotionTypeValues,
|
||||||
} from "../../../promotion"
|
} from "../../../promotion"
|
||||||
@@ -64,6 +65,7 @@ export interface AdminGetPromotionsParams
|
|||||||
export interface AdminGetPromotionRuleParams {
|
export interface AdminGetPromotionRuleParams {
|
||||||
promotion_type?: PromotionTypeValues
|
promotion_type?: PromotionTypeValues
|
||||||
application_method_type?: ApplicationMethodTypeValues
|
application_method_type?: ApplicationMethodTypeValues
|
||||||
|
application_method_target_type?: ApplicationMethodTargetTypeValues
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminGetPromotionRuleTypeParams extends SelectParams {
|
export interface AdminGetPromotionRuleTypeParams extends SelectParams {
|
||||||
@@ -80,4 +82,5 @@ export interface AdminGetPromotionsRuleValueParams extends FindParams {
|
|||||||
* Filter by rule value.
|
* Filter by rule value.
|
||||||
*/
|
*/
|
||||||
value?: string | string[]
|
value?: string | string[]
|
||||||
|
application_method_target_type?: ApplicationMethodTargetTypeValues
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { ModuleJoinerConfig } from "@medusajs/framework/types"
|
||||||
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
|
export const CartShippingOption: ModuleJoinerConfig = {
|
||||||
|
isLink: true,
|
||||||
|
isReadOnlyLink: true,
|
||||||
|
extends: [
|
||||||
|
{
|
||||||
|
serviceName: Modules.CART,
|
||||||
|
entity: "ShippingMethod",
|
||||||
|
relationship: {
|
||||||
|
serviceName: Modules.FULFILLMENT,
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "shipping_option_id",
|
||||||
|
alias: "shipping_option",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "ShippingOptions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ export * from "./cart-customer"
|
|||||||
export * from "./cart-product"
|
export * from "./cart-product"
|
||||||
export * from "./cart-region"
|
export * from "./cart-region"
|
||||||
export * from "./cart-sales-channel"
|
export * from "./cart-sales-channel"
|
||||||
|
export * from "./cart-shipping-option"
|
||||||
export * from "./inventory-level-stock-location"
|
export * from "./inventory-level-stock-location"
|
||||||
export * from "./line-item-adjustment-promotion"
|
export * from "./line-item-adjustment-promotion"
|
||||||
export * from "./order-customer"
|
export * from "./order-customer"
|
||||||
|
|||||||
+1
-6
@@ -1,10 +1,5 @@
|
|||||||
import { IPromotionModuleService } from "@medusajs/framework/types"
|
import { IPromotionModuleService } from "@medusajs/framework/types"
|
||||||
import {
|
import { ApplicationMethodType, Modules, PromotionStatus, PromotionType, } from "@medusajs/framework/utils"
|
||||||
ApplicationMethodType,
|
|
||||||
Modules,
|
|
||||||
PromotionStatus,
|
|
||||||
PromotionType,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import { moduleIntegrationTestRunner, SuiteOptions } from "@medusajs/test-utils"
|
import { moduleIntegrationTestRunner, SuiteOptions } from "@medusajs/test-utils"
|
||||||
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
||||||
import { createDefaultPromotion } from "../../../__fixtures__/promotion"
|
import { createDefaultPromotion } from "../../../__fixtures__/promotion"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from "./buy-get"
|
export * from "./buy-get"
|
||||||
export * from "./line-items"
|
export * from "./line-items"
|
||||||
|
export * from "./shipping-methods"
|
||||||
export * from "./usage"
|
export * from "./usage"
|
||||||
@@ -6,11 +6,11 @@ import {
|
|||||||
import {
|
import {
|
||||||
ApplicationMethodAllocation,
|
ApplicationMethodAllocation,
|
||||||
ApplicationMethodTargetType,
|
ApplicationMethodTargetType,
|
||||||
|
ApplicationMethodTargetType as TargetType,
|
||||||
|
calculateAdjustmentAmountFromPromotion,
|
||||||
ComputedActions,
|
ComputedActions,
|
||||||
MathBN,
|
MathBN,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
ApplicationMethodTargetType as TargetType,
|
|
||||||
calculateAdjustmentAmountFromPromotion,
|
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { areRulesValidForContext } from "../validations"
|
import { areRulesValidForContext } from "../validations"
|
||||||
import { computeActionForBudgetExceeded } from "./usage"
|
import { computeActionForBudgetExceeded } from "./usage"
|
||||||
@@ -43,29 +43,6 @@ export function getComputedActionsForItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComputedActionsForShippingMethods(
|
|
||||||
promotion: PromotionTypes.PromotionDTO,
|
|
||||||
shippingMethods: PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS],
|
|
||||||
appliedPromotionsMap: Map<string, number>
|
|
||||||
): PromotionTypes.ComputeActions[] {
|
|
||||||
validateContext("shipping_methods", shippingMethods)
|
|
||||||
|
|
||||||
return applyPromotionToItems(promotion, shippingMethods, appliedPromotionsMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getComputedActionsForOrder(
|
|
||||||
promotion: PromotionTypes.PromotionDTO,
|
|
||||||
itemApplicationContext: PromotionTypes.ComputeActionContext,
|
|
||||||
methodIdPromoValueMap: Map<string, number>
|
|
||||||
): PromotionTypes.ComputeActions[] {
|
|
||||||
return getComputedActionsForItems(
|
|
||||||
promotion,
|
|
||||||
itemApplicationContext[TargetType.ITEMS],
|
|
||||||
methodIdPromoValueMap,
|
|
||||||
ApplicationMethodAllocation.ACROSS
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPromotionToItems(
|
function applyPromotionToItems(
|
||||||
promotion: PromotionTypes.PromotionDTO,
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
items:
|
items:
|
||||||
|
|||||||
@@ -123,20 +123,13 @@ export function applyPromotionToShippingMethods(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const promotionValue = applicationMethod?.value ?? 0
|
const promotionValue = applicationMethod?.value ?? 0
|
||||||
const applicableTotal = method.subtotal
|
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||||
|
const applicableTotal = MathBN.sub(method.subtotal, appliedPromoValue)
|
||||||
|
|
||||||
const div = MathBN.eq(totalApplicableValue, 0) ? 1 : totalApplicableValue
|
let applicablePromotionValue = MathBN.mult(MathBN.div(applicableTotal, totalApplicableValue), promotionValue)
|
||||||
let applicablePromotionValue = MathBN.sub(
|
|
||||||
MathBN.mult(MathBN.div(applicableTotal, div), promotionValue),
|
|
||||||
appliedPromoValue
|
|
||||||
)
|
|
||||||
|
|
||||||
if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) {
|
if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) {
|
||||||
applicablePromotionValue = MathBN.sub(
|
applicablePromotionValue = MathBN.mult(MathBN.div(promotionValue, 100), applicableTotal)
|
||||||
MathBN.mult(MathBN.div(promotionValue, 100), applicableTotal),
|
|
||||||
appliedPromoValue
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const amount = MathBN.min(applicablePromotionValue, applicableTotal)
|
const amount = MathBN.min(applicablePromotionValue, applicableTotal)
|
||||||
|
|||||||
Reference in new issue
Block a user