feat(dashboard,medusa): Promotion Campaign fixes (#7337)
* chore(medusa): strict zod versions in workspace * feat(dashboard): add campaign create to promotion UI * wip * fix(medusa): Missing middlewares export (#7289) * fix(docblock-generator): fix how type names created from Zod objects are inferred (#7292) * feat(api-ref): show schema of a tag (#7297) * feat: Add support for sendgrid and logger notification providers (#7290) * feat: Add support for sendgrid and logger notification providers * fix: changes based on PR review * chore: add action to automatically label docs (#7284) * chore: add action to automatically label docs * removes the paths param * docs: preparations for preview (#7267) * configured base paths + added development banner * fix typelist site url * added navbar and sidebar badges * configure algolia filters * remove AI assistant * remove unused imports * change navbar text and badge * lint fixes * fix build error * add to api reference rewrites * fix build error * fix build errors in user-guide * fix feedback component * add parent title to pagination * added breadcrumbs component * remove user-guide links * resolve todos * fix details about authentication * change documentation title * lint content * chore: fix bug with form reset * chore: address reviews * chore: fix specs * chore: loads of FE fixes + BE adds * chore: add more polishes + reorg files * chore: fixes to promotions modal * chore: cleanup * chore: cleanup * chore: fix build * chore: fkix cart spec * chore: fix module tests * chore: fix moar tests * wip * chore: templates + fixes + migrate currency * chore: fix build, add validation for max_quantity * chore: allow removing campaigns * chore: fix specs * chore: scope campaigns based on currency * remove console logs * chore: add translations + update keys * chore: move over filesfrom v2 to routes * chore(dashboard): Delete old translation files (#7423) * feat(dashboard,admin-sdk,admin-shared,admin-vite-plugin): Add support for UI extensions (#7383) * intial work * update lock * add routes and fix HMR of configs * cleanup * rm imports * rm debug from plugin * address feedback * address feedback * temp skip specs --------- Co-authored-by: Adrien de Peretti <adrien.deperetti@gmail.com> Co-authored-by: Shahed Nasser <shahednasser@gmail.com> Co-authored-by: Stevche Radevski <sradevski@live.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { CampaignBudgetType } from "@medusajs/utils"
|
||||
import { CampaignBudgetType, isPresent } from "@medusajs/utils"
|
||||
import { z } from "zod"
|
||||
import { createFindParams, createSelectParams } from "../../utils/validators"
|
||||
|
||||
@@ -10,44 +10,70 @@ export type AdminGetCampaignsParamsType = z.infer<
|
||||
export const AdminGetCampaignsParams = createFindParams({
|
||||
offset: 0,
|
||||
limit: 50,
|
||||
}).merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
campaign_identifier: z.string().optional(),
|
||||
currency: z.string().optional(),
|
||||
$and: z.lazy(() => AdminGetCampaignsParams.array()).optional(),
|
||||
$or: z.lazy(() => AdminGetCampaignsParams.array()).optional(),
|
||||
})
|
||||
.merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
campaign_identifier: z.string().optional(),
|
||||
budget: z
|
||||
.object({
|
||||
currency_code: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
$and: z.lazy(() => AdminGetCampaignsParams.array()).optional(),
|
||||
$or: z.lazy(() => AdminGetCampaignsParams.array()).optional(),
|
||||
})
|
||||
)
|
||||
.strict()
|
||||
|
||||
export const CreateCampaignBudget = z
|
||||
.object({
|
||||
type: z.nativeEnum(CampaignBudgetType),
|
||||
limit: z.number().optional().nullable(),
|
||||
currency_code: z.string().optional().nullable(),
|
||||
})
|
||||
)
|
||||
.strict()
|
||||
.refine(
|
||||
(data) =>
|
||||
data.type !== CampaignBudgetType.SPEND || isPresent(data.currency_code),
|
||||
{
|
||||
path: ["currency_code"],
|
||||
message: `currency_code is required when budget type is ${CampaignBudgetType.SPEND}`,
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) =>
|
||||
data.type !== CampaignBudgetType.USAGE || !isPresent(data.currency_code),
|
||||
{
|
||||
path: ["currency_code"],
|
||||
message: `currency_code should not be present when budget type is ${CampaignBudgetType.USAGE}`,
|
||||
}
|
||||
)
|
||||
|
||||
const CreateCampaignBudget = z.object({
|
||||
type: z.nativeEnum(CampaignBudgetType),
|
||||
limit: z.number(),
|
||||
})
|
||||
|
||||
const UpdateCampaignBudget = z.object({
|
||||
type: z.nativeEnum(CampaignBudgetType).optional(),
|
||||
limit: z.number().optional(),
|
||||
})
|
||||
export const UpdateCampaignBudget = z
|
||||
.object({
|
||||
limit: z.number().optional().nullable(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type AdminCreateCampaignType = z.infer<typeof AdminCreateCampaign>
|
||||
export const AdminCreateCampaign = z.object({
|
||||
name: z.string(),
|
||||
campaign_identifier: z.string(),
|
||||
description: z.string().optional(),
|
||||
currency: z.string().optional(),
|
||||
budget: CreateCampaignBudget.optional(),
|
||||
starts_at: z.coerce.date().optional(),
|
||||
ends_at: z.coerce.date().optional(),
|
||||
promotions: z.array(z.object({ id: z.string() })).optional(),
|
||||
})
|
||||
export const AdminCreateCampaign = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
campaign_identifier: z.string(),
|
||||
description: z.string().optional(),
|
||||
budget: CreateCampaignBudget.optional(),
|
||||
starts_at: z.coerce.date().optional(),
|
||||
ends_at: z.coerce.date().optional(),
|
||||
promotions: z.array(z.object({ id: z.string() })).optional(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type AdminUpdateCampaignType = z.infer<typeof AdminUpdateCampaign>
|
||||
export const AdminUpdateCampaign = z.object({
|
||||
name: z.string().optional(),
|
||||
campaign_identifier: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
currency: z.string().optional(),
|
||||
budget: UpdateCampaignBudget.optional(),
|
||||
starts_at: z.coerce.date().optional(),
|
||||
ends_at: z.coerce.date().optional(),
|
||||
|
||||
@@ -57,6 +57,7 @@ export const GET = async (
|
||||
attribute: disguisedRule.id,
|
||||
attribute_label: disguisedRule.label,
|
||||
field_type: disguisedRule.field_type,
|
||||
hydrate: disguisedRule.hydrate || false,
|
||||
operator: RuleOperator.EQ,
|
||||
operator_label: operatorsMap[RuleOperator.EQ].label,
|
||||
values,
|
||||
@@ -67,9 +68,11 @@ export const GET = async (
|
||||
continue
|
||||
}
|
||||
|
||||
for (const promotionRule of promotionRules) {
|
||||
for (const promotionRule of [...promotionRules, ...transformedRules]) {
|
||||
const currentRuleAttribute = ruleAttributes.find(
|
||||
(attr) => attr.value === promotionRule.attribute
|
||||
(attr) =>
|
||||
attr.value === promotionRule.attribute ||
|
||||
attr.value === promotionRule.attribute
|
||||
)
|
||||
|
||||
if (!currentRuleAttribute) {
|
||||
@@ -77,6 +80,11 @@ export const GET = async (
|
||||
}
|
||||
|
||||
const queryConfig = ruleQueryConfigurations[currentRuleAttribute.id]
|
||||
|
||||
if (!queryConfig) {
|
||||
continue
|
||||
}
|
||||
|
||||
const rows = await remoteQuery(
|
||||
remoteQueryObjectFromString({
|
||||
entryPoint: queryConfig.entryPoint,
|
||||
@@ -101,15 +109,17 @@ export const GET = async (
|
||||
label: valueLabelMap.get(value.value) || value.value,
|
||||
}))
|
||||
|
||||
transformedRules.push({
|
||||
...promotionRule,
|
||||
attribute_label: currentRuleAttribute.label,
|
||||
field_type: currentRuleAttribute.field_type,
|
||||
operator_label:
|
||||
operatorsMap[promotionRule.operator]?.label || promotionRule.operator,
|
||||
disguised: false,
|
||||
required: currentRuleAttribute.required || false,
|
||||
})
|
||||
if (!currentRuleAttribute.hydrate) {
|
||||
transformedRules.push({
|
||||
...promotionRule,
|
||||
attribute_label: currentRuleAttribute.label,
|
||||
field_type: currentRuleAttribute.field_type,
|
||||
operator_label:
|
||||
operatorsMap[promotionRule.operator]?.label || promotionRule.operator,
|
||||
disguised: false,
|
||||
required: currentRuleAttribute.required || false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredRules.length && !transformedRules.length) {
|
||||
@@ -124,6 +134,7 @@ export const GET = async (
|
||||
values: [],
|
||||
disguised: true,
|
||||
required: true,
|
||||
hydrate: false,
|
||||
})
|
||||
|
||||
continue
|
||||
|
||||
+8
-1
@@ -19,6 +19,13 @@ export const GET = async (
|
||||
const { rule_type: ruleType, rule_attribute_id: ruleAttributeId } = req.params
|
||||
const queryConfig = ruleQueryConfigurations[ruleAttributeId]
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const filterableFields = req.filterableFields
|
||||
|
||||
if (filterableFields.value) {
|
||||
filterableFields[queryConfig.valueAttr] = filterableFields.value
|
||||
|
||||
delete filterableFields.value
|
||||
}
|
||||
|
||||
validateRuleType(ruleType)
|
||||
validateRuleAttribute(ruleType, ruleAttributeId)
|
||||
@@ -27,7 +34,7 @@ export const GET = async (
|
||||
remoteQueryObjectFromString({
|
||||
entryPoint: queryConfig.entryPoint,
|
||||
variables: {
|
||||
filters: req.filterableFields,
|
||||
filters: filterableFields,
|
||||
...req.remoteQueryConfig.pagination,
|
||||
},
|
||||
fields: [queryConfig.labelAttr, queryConfig.valueAttr],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export enum DisguisedRule {
|
||||
APPLY_TO_QUANTITY = "apply_to_quantity",
|
||||
BUY_RULES_MIN_QUANTITY = "buy_rules_min_quantity",
|
||||
CURRENCY_CODE = "currency_code",
|
||||
}
|
||||
|
||||
export const disguisedRulesMap = {
|
||||
@@ -10,38 +11,48 @@ export const disguisedRulesMap = {
|
||||
[DisguisedRule.BUY_RULES_MIN_QUANTITY]: {
|
||||
relation: "application_method",
|
||||
},
|
||||
[DisguisedRule.CURRENCY_CODE]: {
|
||||
relation: "application_method",
|
||||
},
|
||||
}
|
||||
|
||||
const ruleAttributes = [
|
||||
{
|
||||
id: "currency",
|
||||
value: "currency_code",
|
||||
label: "Currency code",
|
||||
id: DisguisedRule.CURRENCY_CODE,
|
||||
value: DisguisedRule.CURRENCY_CODE,
|
||||
label: "Currency Code",
|
||||
field_type: "select",
|
||||
required: true,
|
||||
disguised: true,
|
||||
hydrate: true,
|
||||
},
|
||||
{
|
||||
id: "customer_group",
|
||||
value: "customer_group.id",
|
||||
value: "customer.groups.id",
|
||||
label: "Customer Group",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
{
|
||||
id: "region",
|
||||
value: "region.id",
|
||||
label: "Region",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
{
|
||||
id: "country",
|
||||
value: "shipping_address.country_code",
|
||||
label: "Country",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
{
|
||||
id: "sales_channel",
|
||||
value: "sales_channel.id",
|
||||
value: "sales_channel_id",
|
||||
label: "Sales Channel",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -51,30 +62,35 @@ const commonAttributes = [
|
||||
value: "items.product.id",
|
||||
label: "Product",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
{
|
||||
id: "product_category",
|
||||
value: "items.product.categories.id",
|
||||
label: "Product Category",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
{
|
||||
id: "product_collection",
|
||||
value: "items.product.collection_id",
|
||||
label: "Product Collection",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
{
|
||||
id: "product_type",
|
||||
value: "items.product.type_id",
|
||||
label: "Product Type",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
{
|
||||
id: "product_tag",
|
||||
value: "items.product.tags.id",
|
||||
label: "Product Tag",
|
||||
required: false,
|
||||
field_type: "multiselect",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ export const ruleQueryConfigurations = {
|
||||
labelAttr: "name",
|
||||
valueAttr: "id",
|
||||
},
|
||||
currency: {
|
||||
currency_code: {
|
||||
entryPoint: "currency",
|
||||
labelAttr: "code",
|
||||
labelAttr: "name",
|
||||
valueAttr: "code",
|
||||
},
|
||||
customer_group: {
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
ApplicationMethodAllocation,
|
||||
ApplicationMethodTargetType,
|
||||
ApplicationMethodType,
|
||||
CampaignBudgetType,
|
||||
PromotionRuleOperator,
|
||||
PromotionType,
|
||||
} from "@medusajs/utils"
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
createOperatorMap,
|
||||
createSelectParams,
|
||||
} from "../../utils/validators"
|
||||
import { AdminCreateCampaign } from "../campaigns/validators"
|
||||
|
||||
export type AdminGetPromotionParamsType = z.infer<
|
||||
typeof AdminGetPromotionParams
|
||||
@@ -30,6 +30,11 @@ export const AdminGetPromotionsParams = createFindParams({
|
||||
q: z.string().optional(),
|
||||
code: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
campaign_id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
application_method: z
|
||||
.object({
|
||||
currency_code: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
})
|
||||
.optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
@@ -58,6 +63,7 @@ export const AdminGetPromotionsRuleValueParams = createFindParams({
|
||||
}).merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
value: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
})
|
||||
)
|
||||
|
||||
@@ -69,7 +75,7 @@ export const AdminCreatePromotionRule = z
|
||||
operator: z.nativeEnum(PromotionRuleOperator),
|
||||
description: z.string().optional(),
|
||||
attribute: z.string(),
|
||||
values: z.array(z.string()),
|
||||
values: z.union([z.string(), z.array(z.string())]),
|
||||
})
|
||||
.strict()
|
||||
|
||||
@@ -82,7 +88,7 @@ export const AdminUpdatePromotionRule = z
|
||||
operator: z.nativeEnum(PromotionRuleOperator).optional(),
|
||||
description: z.string().optional(),
|
||||
attribute: z.string().optional(),
|
||||
values: z.array(z.string()).optional(),
|
||||
values: z.union([z.string(), z.array(z.string())]),
|
||||
})
|
||||
.strict()
|
||||
|
||||
@@ -93,12 +99,12 @@ export const AdminCreateApplicationMethod = z
|
||||
.object({
|
||||
description: z.string().optional(),
|
||||
value: z.number(),
|
||||
max_quantity: z.number().optional(),
|
||||
currency_code: z.string(),
|
||||
max_quantity: z.number().optional().nullable(),
|
||||
type: z.nativeEnum(ApplicationMethodType),
|
||||
target_type: z.nativeEnum(ApplicationMethodTargetType),
|
||||
allocation: z.nativeEnum(ApplicationMethodAllocation).optional(),
|
||||
target_rules: z.array(AdminCreatePromotionRule).optional(),
|
||||
|
||||
buy_rules: z.array(AdminCreatePromotionRule).optional(),
|
||||
apply_to_quantity: z.number().optional(),
|
||||
buy_rules_min_quantity: z.number().optional(),
|
||||
@@ -111,8 +117,9 @@ export type AdminUpdateApplicationMethodType = z.infer<
|
||||
export const AdminUpdateApplicationMethod = z
|
||||
.object({
|
||||
description: z.string().optional(),
|
||||
value: z.string().optional(),
|
||||
max_quantity: z.number().optional(),
|
||||
value: z.number().optional(),
|
||||
max_quantity: z.number().optional().nullable(),
|
||||
currency_code: z.string().optional(),
|
||||
type: z.nativeEnum(ApplicationMethodType).optional(),
|
||||
target_type: z.nativeEnum(ApplicationMethodTargetType).optional(),
|
||||
allocation: z.nativeEnum(ApplicationMethodAllocation).optional(),
|
||||
@@ -140,23 +147,6 @@ const promoRefinement = (promo) => {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ideally we don't allow for creation of campaigns through promotions, it should be the other way around.
|
||||
const CreateCampaignBudget = z.object({
|
||||
type: z.nativeEnum(CampaignBudgetType),
|
||||
limit: z.number(),
|
||||
})
|
||||
|
||||
export type AdminCreateCampaignType = z.infer<typeof AdminCreateCampaign>
|
||||
export const AdminCreateCampaign = z.object({
|
||||
name: z.string(),
|
||||
campaign_identifier: z.string(),
|
||||
description: z.string().optional(),
|
||||
currency: z.string().optional(),
|
||||
budget: CreateCampaignBudget.optional(),
|
||||
starts_at: z.coerce.date().optional(),
|
||||
ends_at: z.coerce.date().optional(),
|
||||
})
|
||||
|
||||
export type AdminCreatePromotionType = z.infer<typeof AdminCreatePromotion>
|
||||
export const AdminCreatePromotion = z
|
||||
.object({
|
||||
|
||||
Reference in New Issue
Block a user