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:
William Bouchard authored and GitHub committed 2025-08-22 16:30:30 -04:00
1 parent 92e3b2b432
commit 486621383a
22 files changed
+474 -331

No files matched your search

+10
View File
@@ -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,
}) })
@@ -7516,14 +7516,47 @@
"target-rules": { "target-rules": {
"type": "object", "type": "object",
"properties": { "properties": {
"title": { "order": {
"type": "string" "type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": ["title", "description"],
"additionalProperties": false
}, },
"description": { "shipping_methods": {
"type": "string" "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": ["title", "description"], "required": ["order", "shipping_methods", "items"],
"additionalProperties": false "additionalProperties": false
}, },
"buy-rules": { "buy-rules": {
@@ -2010,8 +2010,18 @@
"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": {
"title": "What items will the promotion be applied to?", "order": {
"description": "The promotion will be applied to items that match the following conditions." "title": "What items will the promotion be applied to?",
"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?",
@@ -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),
}) })
@@ -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>
@@ -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,
} }
) )
}, },
@@ -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>
@@ -678,60 +678,62 @@ 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"
) && ( ) && (
<Form.Field <>
control={form.control} <Divider />
name="application_method.type" <Form.Field
render={({ field }) => { control={form.control}
return ( name="application_method.type"
<Form.Item> render={({ field }) => {
<Form.Label> return (
{t("promotions.fields.value_type")} <Form.Item>
</Form.Label> <Form.Label>
<Form.Control> {t("promotions.fields.value_type")}
<RadioGroup </Form.Label>
className="flex gap-y-3" <Form.Control>
{...field} <RadioGroup
onValueChange={field.onChange} className="flex gap-y-3"
> {...field}
<RadioGroup.ChoiceBox onValueChange={field.onChange}
value={"fixed"} >
label={t( <RadioGroup.ChoiceBox
"promotions.form.value_type.fixed.title" value={"fixed"}
)} label={t(
description={t( "promotions.form.value_type.fixed.title"
"promotions.form.value_type.fixed.description" )}
)} description={t(
className={clx("basis-1/2")} "promotions.form.value_type.fixed.description"
/> )}
className={clx("basis-1/2")}
/>
<RadioGroup.ChoiceBox <RadioGroup.ChoiceBox
value={"percentage"} value={"percentage"}
label={t( label={t(
"promotions.form.value_type.percentage.title" "promotions.form.value_type.percentage.title"
)} )}
description={t( description={t(
"promotions.form.value_type.percentage.description" "promotions.form.value_type.percentage.description"
)} )}
className={clx("basis-1/2")} className={clx("basis-1/2")}
/> />
</RadioGroup> </RadioGroup>
</Form.Control> </Form.Control>
<Form.ErrorMessage /> <Form.ErrorMessage />
</Form.Item> </Form.Item>
) )
}} }}
/> />
</>
)} )}
<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,48 +808,48 @@ export const CreatePromotionForm = () => {
) )
}} }}
/> />
)} </>
)}
{isTypeStandard && watchAllocation === "each" && ( {isTypeStandard && watchAllocation === "each" && (
<Form.Field <Form.Field
control={form.control} control={form.control}
name="application_method.max_quantity" name="application_method.max_quantity"
render={({ field }) => { render={({ field }) => {
return ( return (
<Form.Item className="basis-1/2"> <Form.Item className="basis-1/2">
<Form.Label> <Form.Label>
{t("promotions.form.max_quantity.title")} {t("promotions.form.max_quantity.title")}
</Form.Label> </Form.Label>
<Form.Control> <Form.Control>
<Input <Input
{...form.register( {...form.register(
"application_method.max_quantity", "application_method.max_quantity",
{ valueAsNumber: true } { valueAsNumber: true }
)} )}
type="number" type="number"
min={1} min={1}
placeholder="3" placeholder="3"
/> />
</Form.Control> </Form.Control>
<Text <Text
size="small" size="small"
leading="compact" leading="compact"
className="text-ui-fg-subtle" className="text-ui-fg-subtle"
> >
<Trans <Trans
t={t} t={t}
i18nKey="promotions.form.max_quantity.description" i18nKey="promotions.form.max_quantity.description"
components={[<br key="break" />]} components={[<br key="break" />]}
/> />
</Text> </Text>
</Form.Item> </Form.Item>
) )
}} }}
/> />
)} )}
</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"}
@@ -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,
},
},
},
] ]
@@ -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>
@@ -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,128 +218,139 @@ export const EditPromotionDetailsForm = ({
</Text> </Text>
</div> </div>
<Form.Field {promotion.application_method?.target_type !==
control={form.control} "shipping_methods" && (
name="value_type" <>
render={({ field }) => { <Form.Field
return ( control={form.control}
<Form.Item> name="value_type"
<Form.Label>{t("promotions.fields.value_type")}</Form.Label> render={({ field }) => {
<Form.Control> return (
<RadioGroup <Form.Item>
className="flex-col gap-y-3" <Form.Label>
{...field} {t("promotions.fields.value_type")}
onValueChange={field.onChange} </Form.Label>
> <Form.Control>
<RadioGroup.ChoiceBox <RadioGroup
value={"fixed"} className="flex-col gap-y-3"
label={t("promotions.form.value_type.fixed.title")} {...field}
description={t( onValueChange={field.onChange}
"promotions.form.value_type.fixed.description" >
)} <RadioGroup.ChoiceBox
/> value={"fixed"}
label={t(
"promotions.form.value_type.fixed.title"
)}
description={t(
"promotions.form.value_type.fixed.description"
)}
/>
<RadioGroup.ChoiceBox <RadioGroup.ChoiceBox
value={"percentage"} value={"percentage"}
label={t( label={t(
"promotions.form.value_type.percentage.title" "promotions.form.value_type.percentage.title"
)} )}
description={t( description={t(
"promotions.form.value_type.percentage.description" "promotions.form.value_type.percentage.description"
)} )}
/> />
</RadioGroup> </RadioGroup>
</Form.Control> </Form.Control>
<Form.ErrorMessage /> <Form.ErrorMessage />
</Form.Item> </Form.Item>
) )
}} }}
/> />
<Form.Field
control={form.control}
name="value"
render={({ field: { onChange, ...field } }) => {
const currencyCode =
promotion.application_method?.currency_code ?? "USD"
<Form.Field return (
control={form.control} <Form.Item>
name="value" <Form.Label>
render={({ field: { onChange, ...field } }) => { {isFixedValueType
const currencyCode = ? t("fields.amount")
promotion.application_method?.currency_code ?? "USD" : t("fields.percentage")}
</Form.Label>
return ( <Form.Control>
<Form.Item> {isFixedValueType ? (
<Form.Label> <CurrencyInput
{isFixedValueType min={0}
? t("fields.amount") onValueChange={(val) =>
: t("fields.percentage")} onChange(val ? parseInt(val) : null)
</Form.Label> }
<Form.Control> code={currencyCode}
{isFixedValueType ? ( symbol={getCurrencySymbol(currencyCode)}
<CurrencyInput {...field}
min={0} value={field.value}
onValueChange={(val) => />
onChange(val ? parseInt(val) : null) ) : (
} <DeprecatedPercentageInput
code={currencyCode} key="amount"
symbol={getCurrencySymbol(currencyCode)} min={0}
{...field} max={100}
value={field.value} {...field}
/> value={field.value || ""}
) : ( onChange={(e) => {
<DeprecatedPercentageInput onChange(
key="amount" e.target.value === ""
min={0} ? null
max={100} : parseInt(e.target.value)
{...field} )
value={field.value || ""} }}
onChange={(e) => { />
onChange(
e.target.value === ""
? null
: parseInt(e.target.value)
)
}}
/>
)}
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="allocation"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("promotions.fields.allocation")}</Form.Label>
<Form.Control>
<RadioGroup
className="flex-col 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"
)} )}
/> </Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="allocation"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>
{t("promotions.fields.allocation")}
</Form.Label>
<Form.Control>
<RadioGroup
className="flex-col 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"
)}
/>
<RadioGroup.ChoiceBox <RadioGroup.ChoiceBox
value={"across"} value={"across"}
label={t("promotions.form.allocation.across.title")} label={t(
description={t( "promotions.form.allocation.across.title"
"promotions.form.allocation.across.description" )}
)} description={t(
/> "promotions.form.allocation.across.description"
</RadioGroup> )}
</Form.Control> />
<Form.ErrorMessage /> </RadioGroup>
</Form.Item> </Form.Control>
) <Form.ErrorMessage />
}} </Form.Item>
/> )
}}
/>
</>
)}
</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",
+63 -58
View File
@@ -15,27 +15,27 @@ export class Promotion {
} }
/** /**
* This method retrieves a promotion by its ID. It sends a request to the * This method retrieves a promotion by its ID. It sends a request to the
* [Retrieve Promotion](https://docs.medusajs.com/api/admin#promotions_getpromotionsid) * [Retrieve Promotion](https://docs.medusajs.com/api/admin#promotions_getpromotionsid)
* API route. * API route.
* *
* @param id - The promotion's ID. * @param id - The promotion's ID.
* @param query - Configure the fields to retrieve in the promotion. * @param query - Configure the fields to retrieve in the promotion.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The promotion's details. * @returns The promotion's details.
* *
* @example * @example
* To retrieve a promotion by its ID: * To retrieve a promotion by its ID:
* *
* ```ts * ```ts
* sdk.admin.promotion.retrieve("promo_123") * sdk.admin.promotion.retrieve("promo_123")
* .then(({ promotion }) => { * .then(({ promotion }) => {
* console.log(promotion) * console.log(promotion)
* }) * })
* ``` * ```
* *
* To specify the fields and relations to retrieve: * To specify the fields and relations to retrieve:
* *
* ```ts * ```ts
* sdk.admin.promotion.retrieve("promo_123", { * sdk.admin.promotion.retrieve("promo_123", {
* fields: "id,*application_method" * fields: "id,*application_method"
@@ -44,7 +44,7 @@ export class Promotion {
* console.log(promotion) * console.log(promotion)
* }) * })
* ``` * ```
* *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations). * Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/ */
async retrieve( async retrieve(
@@ -62,28 +62,28 @@ export class Promotion {
} }
/** /**
* This method retrieves a list of promotions. It sends a request to the * This method retrieves a list of promotions. It sends a request to the
* [List Promotions](https://docs.medusajs.com/api/admin#promotions_getpromotions) * [List Promotions](https://docs.medusajs.com/api/admin#promotions_getpromotions)
* API route. * API route.
* *
* @param query - Filters and pagination configurations. * @param query - Filters and pagination configurations.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The list of promotions. * @returns The list of promotions.
* *
* @example * @example
* To retrieve the list of promotions: * To retrieve the list of promotions:
* *
* ```ts * ```ts
* sdk.admin.promotion.list() * sdk.admin.promotion.list()
* .then(({ promotions, count, limit, offset }) => { * .then(({ promotions, count, limit, offset }) => {
* console.log(promotions) * console.log(promotions)
* }) * })
* ``` * ```
* *
* To configure the pagination, pass the `limit` and `offset` query parameters. * To configure the pagination, pass the `limit` and `offset` query parameters.
* *
* For example, to retrieve only 10 items and skip 10 items: * For example, to retrieve only 10 items and skip 10 items:
* *
* ```ts * ```ts
* sdk.admin.promotion.list({ * sdk.admin.promotion.list({
* limit: 10, * limit: 10,
@@ -93,10 +93,10 @@ export class Promotion {
* console.log(promotions) * console.log(promotions)
* }) * })
* ``` * ```
* *
* Using the `fields` query parameter, you can specify the fields and relations to retrieve * Using the `fields` query parameter, you can specify the fields and relations to retrieve
* in each promotion: * in each promotion:
* *
* ```ts * ```ts
* sdk.admin.promotion.list({ * sdk.admin.promotion.list({
* fields: "id,*application_method" * fields: "id,*application_method"
@@ -105,7 +105,7 @@ export class Promotion {
* console.log(promotions) * console.log(promotions)
* }) * })
* ``` * ```
* *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations). * Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/ */
async list( async list(
@@ -122,16 +122,16 @@ export class Promotion {
} }
/** /**
* This method creates a new promotion. It sends a request to the * This method creates a new promotion. It sends a request to the
* [Create Promotion](https://docs.medusajs.com/api/admin#promotions_postpromotions) * [Create Promotion](https://docs.medusajs.com/api/admin#promotions_postpromotions)
* API route. * API route.
* *
* @param payload - The promotion to create. * @param payload - The promotion to create.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The promotion's details. * @returns The promotion's details.
* *
* @example * @example
* sdk.admin.promotion.create({ * sdk.admin.promotion.create({
* name: "My Promotion", * name: "My Promotion",
* description: "This is a test promotion", * description: "This is a test promotion",
* code: "PROMO123", * code: "PROMO123",
@@ -157,15 +157,15 @@ export class Promotion {
} }
/** /**
* This method updates a promotion. It sends a request to the * This method updates a promotion. It sends a request to the
* [Update Promotion](https://docs.medusajs.com/api/admin#promotions_postpromotionsid) * [Update Promotion](https://docs.medusajs.com/api/admin#promotions_postpromotionsid)
* API route. * API route.
* *
* @param id - The promotion's ID. * @param id - The promotion's ID.
* @param payload - The details to update in the promotion. * @param payload - The details to update in the promotion.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The promotion's details. * @returns The promotion's details.
* *
* @example * @example
* sdk.admin.promotion.update("promo_123", { * sdk.admin.promotion.update("promo_123", {
* code: "PROMO123", * code: "PROMO123",
@@ -190,14 +190,14 @@ export class Promotion {
} }
/** /**
* This method deletes a promotion. It sends a request to the * This method deletes a promotion. It sends a request to the
* [Delete Promotion](https://docs.medusajs.com/api/admin#promotions_deletepromotionsid) * [Delete Promotion](https://docs.medusajs.com/api/admin#promotions_deletepromotionsid)
* API route. * API route.
* *
* @param id - The promotion's ID. * @param id - The promotion's ID.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The deleted promotion's details. * @returns The deleted promotion's details.
* *
* @example * @example
* sdk.admin.promotion.delete("promo_123") * sdk.admin.promotion.delete("promo_123")
* .then(({ promotion }) => { * .then(({ promotion }) => {
@@ -218,20 +218,20 @@ export class Promotion {
* This method creates and adds rules to a promotion. It can be the promotion's rules, * This method creates and adds rules to a promotion. It can be the promotion's rules,
* or its application method's buy or target rules. That depends on the rule type * or its application method's buy or target rules. That depends on the rule type
* you specify as a parameter. * you specify as a parameter.
* *
* - If you set the `ruleType` to `rules`, the method sends a request to the * - If you set the `ruleType` to `rules`, the method sends a request to the
* [Manage Promotion's Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidrulesbatch). * [Manage Promotion's Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidrulesbatch).
* - If you set the `ruleType` to `buy-rules`, the method sends a request to the * - If you set the `ruleType` to `buy-rules`, the method sends a request to the
* [Manage Promotion's Buy Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidbuyrulesbatch). * [Manage Promotion's Buy Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidbuyrulesbatch).
* - If you set the `ruleType` to `target-rules`, the method sends a request to the * - If you set the `ruleType` to `target-rules`, the method sends a request to the
* [Manage Promotion's Target Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidtargetrulesbatch). * [Manage Promotion's Target Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidtargetrulesbatch).
* *
* @param id - The promotion's ID. * @param id - The promotion's ID.
* @param ruleType - The type of rules to create. * @param ruleType - The type of rules to create.
* @param payload - The rules to create. * @param payload - The rules to create.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The promotion's details. * @returns The promotion's details.
* *
* @example * @example
* sdk.admin.promotion.addRules("promo_123", "rules", { * sdk.admin.promotion.addRules("promo_123", "rules", {
* rules: [ * rules: [
@@ -266,20 +266,20 @@ export class Promotion {
* This method updates the rules of a promotion. It can be the promotion's rules, * This method updates the rules of a promotion. It can be the promotion's rules,
* or its application method's buy or target rules. That depends on the rule type * or its application method's buy or target rules. That depends on the rule type
* you specify as a parameter. * you specify as a parameter.
* *
* - If you set the `ruleType` to `rules`, the method sends a request to the * - If you set the `ruleType` to `rules`, the method sends a request to the
* [Manage Promotion's Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidrulesbatch). * [Manage Promotion's Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidrulesbatch).
* - If you set the `ruleType` to `buy-rules`, the method sends a request to the * - If you set the `ruleType` to `buy-rules`, the method sends a request to the
* [Manage Promotion's Buy Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidbuyrulesbatch). * [Manage Promotion's Buy Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidbuyrulesbatch).
* - If you set the `ruleType` to `target-rules`, the method sends a request to the * - If you set the `ruleType` to `target-rules`, the method sends a request to the
* [Manage Promotion's Target Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidtargetrulesbatch). * [Manage Promotion's Target Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidtargetrulesbatch).
* *
* @param id - The promotion's ID. * @param id - The promotion's ID.
* @param ruleType - The type of rules to update. * @param ruleType - The type of rules to update.
* @param payload - The rules to update. * @param payload - The rules to update.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The promotion's details. * @returns The promotion's details.
* *
* @example * @example
* sdk.admin.promotion.updateRules("promo_123", "rules", { * sdk.admin.promotion.updateRules("promo_123", "rules", {
* rules: [ * rules: [
@@ -313,20 +313,20 @@ export class Promotion {
* This method removes rules from a promotion. It can be the promotion's rules, * This method removes rules from a promotion. It can be the promotion's rules,
* or its application method's buy or target rules. That depends on the rule type * or its application method's buy or target rules. That depends on the rule type
* you specify as a parameter. * you specify as a parameter.
* *
* - If you set the `ruleType` to `rules`, the method sends a request to the * - If you set the `ruleType` to `rules`, the method sends a request to the
* [Manage Promotion's Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidrulesbatch). * [Manage Promotion's Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidrulesbatch).
* - If you set the `ruleType` to `buy-rules`, the method sends a request to the * - If you set the `ruleType` to `buy-rules`, the method sends a request to the
* [Manage Promotion's Buy Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidbuyrulesbatch). * [Manage Promotion's Buy Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidbuyrulesbatch).
* - If you set the `ruleType` to `target-rules`, the method sends a request to the * - If you set the `ruleType` to `target-rules`, the method sends a request to the
* [Manage Promotion's Target Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidtargetrulesbatch). * [Manage Promotion's Target Rules API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsidtargetrulesbatch).
* *
* @param id - The promotion's ID. * @param id - The promotion's ID.
* @param ruleType - The type of rules to remove. * @param ruleType - The type of rules to remove.
* @param payload - The rules to remove. * @param payload - The rules to remove.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The promotion's details. * @returns The promotion's details.
* *
* @example * @example
* sdk.admin.promotion.removeRules("promo_123", "rules", { * sdk.admin.promotion.removeRules("promo_123", "rules", {
* rule_ids: ["rule_123"] * rule_ids: ["rule_123"]
@@ -355,16 +355,16 @@ export class Promotion {
* This method retrieves the rules of a promotion. It can be the promotion's rules, * This method retrieves the rules of a promotion. It can be the promotion's rules,
* or its application method's buy or target rules. That depends on the rule type * or its application method's buy or target rules. That depends on the rule type
* you specify as a parameter. * you specify as a parameter.
* *
* This method sends a request to the * This method sends a request to the
* [List Rules of a Promotion API Route](https://docs.medusajs.com/api/admin#promotions_getpromotionsidrule_type) * [List Rules of a Promotion API Route](https://docs.medusajs.com/api/admin#promotions_getpromotionsidrule_type)
* *
* @param id - The promotion's ID. * @param id - The promotion's ID.
* @param ruleType - The type of rules to retrieve. Can be `rules`, `buy-rules`, or `target-rules`. * @param ruleType - The type of rules to retrieve. Can be `rules`, `buy-rules`, or `target-rules`.
* @param query - Configure the fields to retrieve in the rules. * @param query - Configure the fields to retrieve in the rules.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The promotion's rules. * @returns The promotion's rules.
* *
* @example * @example
* sdk.admin.promotion.listRules("promo_123", "rules") * sdk.admin.promotion.listRules("promo_123", "rules")
* .then(({ rules }) => { * .then(({ rules }) => {
@@ -389,19 +389,20 @@ export class Promotion {
/** /**
* Retrieve a list of potential rule attributes for the promotion and application method types specified in the query parameters. Only the attributes of the rule type specified in the path parameter are retrieved: * Retrieve a list of potential rule attributes for the promotion and application method types specified in the query parameters. Only the attributes of the rule type specified in the path parameter are retrieved:
* *
* - If `rule_type` is `rules`, the attributes of the promotion's type are retrieved. * - If `rule_type` is `rules`, the attributes of the promotion's type are retrieved.
* - If `rule_type` is `target-rules`, the target rules' attributes of the application method's type are retrieved. * - If `rule_type` is `target-rules`, the target rules' attributes of the application method's type are retrieved.
* - If `rule_type` is `buy-rules`, the buy rules' attributes of the application method's type are retrieved. * - If `rule_type` is `buy-rules`, the buy rules' attributes of the application method's type are retrieved.
* *
* This method sends a request to the * This method sends a request to the
* [List Rule Attribute Options API Route](https://docs.medusajs.com/api/admin#promotions_getpromotionsruleattributeoptionsrule_type) * [List Rule Attribute Options API Route](https://docs.medusajs.com/api/admin#promotions_getpromotionsruleattributeoptionsrule_type)
* *
* @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.
* *
* @example * @example
* sdk.admin.promotion.listRuleAttributes("rules", "standard") * sdk.admin.promotion.listRuleAttributes("rules", "standard")
* .then(({ attributes }) => { * .then(({ 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,25 +420,28 @@ 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,
},
} }
) )
} }
/** /**
* Retrieve all potential values for promotion rules and target and buy rules based on the specified rule attribute and type. * Retrieve all potential values for promotion rules and target and buy rules based on the specified rule attribute and type.
* For example, if you provide the ID of the `currency_code` rule attribute, and set `rule_type` to rules, * For example, if you provide the ID of the `currency_code` rule attribute, and set `rule_type` to rules,
* a list of currencies are retrieved in label-value pairs. * a list of currencies are retrieved in label-value pairs.
* *
* This method sends a request to the * This method sends a request to the
* [List Rule Values API Route](https://docs.medusajs.com/api/admin#promotions_getpromotionsrulevalueoptionsrule_typerule_attribute_id) * [List Rule Values API Route](https://docs.medusajs.com/api/admin#promotions_getpromotionsrulevalueoptionsrule_typerule_attribute_id)
* *
* @param ruleType - The type of rules to retrieve the values for. Can be `rules`, `buy-rules`, or `target-rules`. * @param ruleType - The type of rules to retrieve the values for. Can be `rules`, `buy-rules`, or `target-rules`.
* @param ruleValue - The ID of the rule attribute to retrieve the values for. * @param ruleValue - The ID of the rule attribute to retrieve the values for.
* @param query - Configure the fields to retrieve in the rule values. * @param query - Configure the fields to retrieve in the rule values.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The list of rule values. * @returns The list of rule values.
* *
* @example * @example
* sdk.admin.promotion.listRuleValues("rules", "attr_123") * sdk.admin.promotion.listRuleValues("rules", "attr_123")
* .then(({ values }) => { * .then(({ values }) => {
@@ -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,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)