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
+215 -72

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,
}) })
@@ -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."
@@ -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,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"}
@@ -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,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",
+6 -1
View File
@@ -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,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)