feat(medusa,core-flows,types): adds update promotion rule endpoint + workflow (#6702)
what: - adds endpoint + workflow to update promotion rule - adds method in promotion to update promotion rules
This commit is contained in:
1 parent
7be0a2cf6d
commit
e5945479e0
13 files changed
+366
-2
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(medusa,core-flows,types): adds update promotion rule endpoint + workflow
|
||||||
@@ -529,6 +529,113 @@ medusaIntegrationTestRunner({
|
|||||||
expect(promotion.application_method!.buy_rules!.length).toEqual(0)
|
expect(promotion.application_method!.buy_rules!.length).toEqual(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/promotions/:id/rules/batch/update", () => {
|
||||||
|
it("should throw error when required params are missing", async () => {
|
||||||
|
const { response } = await api
|
||||||
|
.post(
|
||||||
|
`/admin/promotions/${standardPromotion.id}/rules/batch/update`,
|
||||||
|
{
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["new value"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(400)
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
type: "invalid_data",
|
||||||
|
message: "id must be a string, id should not be empty",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error when promotion does not exist", async () => {
|
||||||
|
const { response } = await api
|
||||||
|
.post(
|
||||||
|
`/admin/promotions/does-not-exist/rules/batch/update`,
|
||||||
|
{
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
id: standardPromotion.rules[0].id,
|
||||||
|
attribute: "new_attr",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["new value"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(404)
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
type: "not_found",
|
||||||
|
message: "Promotion with id: does-not-exist was not found",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error when promotion rule id does not exist", async () => {
|
||||||
|
const { response } = await api
|
||||||
|
.post(
|
||||||
|
`/admin/promotions/${standardPromotion.id}/rules/batch/update`,
|
||||||
|
{
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
id: "does-not-exist",
|
||||||
|
attribute: "new_attr",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["new value"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(400)
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
type: "invalid_data",
|
||||||
|
message: "Promotion rules with id - does-not-exist not found",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add rules to a promotion successfully", async () => {
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/promotions/${standardPromotion.id}/rules/batch/update`,
|
||||||
|
{
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
id: standardPromotion.rules[0].id,
|
||||||
|
operator: "eq",
|
||||||
|
attribute: "new_attr",
|
||||||
|
values: ["new value"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.promotion).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: standardPromotion.id,
|
||||||
|
rules: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
operator: "eq",
|
||||||
|
attribute: "new_attr",
|
||||||
|
values: [expect.objectContaining({ value: "new value" })],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -5,4 +5,5 @@ export * from "./delete-campaigns"
|
|||||||
export * from "./delete-promotions"
|
export * from "./delete-promotions"
|
||||||
export * from "./remove-rules-from-promotions"
|
export * from "./remove-rules-from-promotions"
|
||||||
export * from "./update-campaigns"
|
export * from "./update-campaigns"
|
||||||
|
export * from "./update-promotion-rules"
|
||||||
export * from "./update-promotions"
|
export * from "./update-promotions"
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import {
|
||||||
|
IPromotionModuleService,
|
||||||
|
UpdatePromotionRulesWorkflowDTO,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const updatePromotionRulesStepId = "update-promotion-rules"
|
||||||
|
export const updatePromotionRulesStep = createStep(
|
||||||
|
updatePromotionRulesStepId,
|
||||||
|
async (input: UpdatePromotionRulesWorkflowDTO, { container }) => {
|
||||||
|
const { data } = input
|
||||||
|
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
const promotionRulesBeforeUpdate = await promotionModule.listPromotionRules(
|
||||||
|
{ id: data.map((d) => d.id) },
|
||||||
|
{ relations: ["values"] }
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedPromotionRules = await promotionModule.updatePromotionRules(
|
||||||
|
data
|
||||||
|
)
|
||||||
|
|
||||||
|
return new StepResponse(updatedPromotionRules, promotionRulesBeforeUpdate)
|
||||||
|
},
|
||||||
|
async (updatedPromotionRules, { container }) => {
|
||||||
|
if (!updatedPromotionRules?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
await promotionModule.updatePromotionRules(
|
||||||
|
updatedPromotionRules.map((rule) => ({
|
||||||
|
id: rule.id,
|
||||||
|
description: rule.description,
|
||||||
|
attribute: rule.attribute,
|
||||||
|
operator: rule.operator,
|
||||||
|
values: rule.values.map((v) => v.value!),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -5,4 +5,5 @@ export * from "./delete-campaigns"
|
|||||||
export * from "./delete-promotions"
|
export * from "./delete-promotions"
|
||||||
export * from "./remove-rules-from-promotions"
|
export * from "./remove-rules-from-promotions"
|
||||||
export * from "./update-campaigns"
|
export * from "./update-campaigns"
|
||||||
|
export * from "./update-promotion-rules"
|
||||||
export * from "./update-promotions"
|
export * from "./update-promotions"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { UpdatePromotionRulesWorkflowDTO } from "@medusajs/types"
|
||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import { updatePromotionRulesStep } from "../steps"
|
||||||
|
|
||||||
|
export const updatePromotionRulesWorkflowId = "update-promotion-rules-workflow"
|
||||||
|
export const updatePromotionRulesWorkflow = createWorkflow(
|
||||||
|
updatePromotionRulesWorkflowId,
|
||||||
|
(
|
||||||
|
input: WorkflowData<UpdatePromotionRulesWorkflowDTO>
|
||||||
|
): WorkflowData<void> => {
|
||||||
|
updatePromotionRulesStep(input)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { updatePromotionRulesWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../../../../../types/routing"
|
||||||
|
import {
|
||||||
|
defaultAdminPromotionFields,
|
||||||
|
defaultAdminPromotionRelations,
|
||||||
|
} from "../../../../query-config"
|
||||||
|
import { AdminPostPromotionsPromotionRulesBatchUpdateReq } from "../../../../validators"
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminPostPromotionsPromotionRulesBatchUpdateReq>,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const id = req.params.id
|
||||||
|
const workflow = updatePromotionRulesWorkflow(req.scope)
|
||||||
|
|
||||||
|
const { errors } = await workflow.run({
|
||||||
|
input: { data: req.validatedBody.rules },
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotionModuleService: IPromotionModuleService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
const promotion = await promotionModuleService.retrieve(id, {
|
||||||
|
select: defaultAdminPromotionFields,
|
||||||
|
relations: defaultAdminPromotionRelations,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).json({ promotion })
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AdminPostPromotionsPromotionReq,
|
AdminPostPromotionsPromotionReq,
|
||||||
AdminPostPromotionsPromotionRulesBatchAddReq,
|
AdminPostPromotionsPromotionRulesBatchAddReq,
|
||||||
AdminPostPromotionsPromotionRulesBatchRemoveReq,
|
AdminPostPromotionsPromotionRulesBatchRemoveReq,
|
||||||
|
AdminPostPromotionsPromotionRulesBatchUpdateReq,
|
||||||
AdminPostPromotionsReq,
|
AdminPostPromotionsReq,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
@@ -63,6 +64,13 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
matcher: "/admin/promotions/:id/buy-rules/batch/add",
|
matcher: "/admin/promotions/:id/buy-rules/batch/add",
|
||||||
middlewares: [transformBody(AdminPostPromotionsPromotionRulesBatchAddReq)],
|
middlewares: [transformBody(AdminPostPromotionsPromotionRulesBatchAddReq)],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/promotions/:id/rules/batch/update",
|
||||||
|
middlewares: [
|
||||||
|
transformBody(AdminPostPromotionsPromotionRulesBatchUpdateReq),
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/admin/promotions/:id/rules/batch/remove",
|
matcher: "/admin/promotions/:id/rules/batch/remove",
|
||||||
|
|||||||
@@ -228,3 +228,34 @@ export class AdminPostPromotionsPromotionRulesBatchRemoveReq {
|
|||||||
@IsString({ each: true })
|
@IsString({ each: true })
|
||||||
rule_ids: string[]
|
rule_ids: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AdminPostPromotionsPromotionRulesBatchUpdateReq {
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => UpdatePromotionRule)
|
||||||
|
rules: UpdatePromotionRule[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdatePromotionRule {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(PromotionRuleOperator)
|
||||||
|
operator?: PromotionRuleOperator
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string | null
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
attribute: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => String)
|
||||||
|
values: string[]
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@ import {
|
|||||||
MedusaError,
|
MedusaError,
|
||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
|
arrayDifference,
|
||||||
|
deduplicate,
|
||||||
|
isDefined,
|
||||||
isString,
|
isString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
@@ -47,6 +50,7 @@ import {
|
|||||||
validatePromotionRuleAttributes,
|
validatePromotionRuleAttributes,
|
||||||
} from "@utils"
|
} from "@utils"
|
||||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||||
|
import { CreatePromotionRuleValueDTO } from "../types/promotion-rule-value"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
baseRepository: DAL.RepositoryService
|
baseRepository: DAL.RepositoryService
|
||||||
@@ -750,6 +754,86 @@ export default class PromotionModuleService<
|
|||||||
return updatedPromotions
|
return updatedPromotions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async updatePromotionRules(
|
||||||
|
data: PromotionTypes.UpdatePromotionRuleDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<PromotionTypes.PromotionRuleDTO[]> {
|
||||||
|
const updatedPromotionRules = await this.updatePromotionRules_(
|
||||||
|
data,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return this.listPromotionRules(
|
||||||
|
{ id: updatedPromotionRules.map((r) => r.id) },
|
||||||
|
{ relations: ["values"] },
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
protected async updatePromotionRules_(
|
||||||
|
data: PromotionTypes.UpdatePromotionRuleDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const promotionRuleIds = data.map((d) => d.id)
|
||||||
|
|
||||||
|
const promotionRules = await this.listPromotionRules(
|
||||||
|
{ id: promotionRuleIds },
|
||||||
|
{ relations: ["values"] },
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
const invalidRuleId = arrayDifference(
|
||||||
|
deduplicate(promotionRuleIds),
|
||||||
|
promotionRules.map((pr) => pr.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (invalidRuleId.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Promotion rules with id - ${invalidRuleId.join(", ")} not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotionRulesMap = new Map<string, PromotionTypes.PromotionRuleDTO>(
|
||||||
|
promotionRules.map((pr) => [pr.id, pr])
|
||||||
|
)
|
||||||
|
|
||||||
|
const rulesToUpdate: PromotionTypes.UpdatePromotionRuleDTO[] = []
|
||||||
|
const ruleValueIdsToDelete: string[] = []
|
||||||
|
const ruleValuesToCreate: CreatePromotionRuleValueDTO[] = []
|
||||||
|
|
||||||
|
for (const promotionRuleData of data) {
|
||||||
|
const { values, ...rest } = promotionRuleData
|
||||||
|
const normalizedValues = Array.isArray(values) ? values : [values]
|
||||||
|
rulesToUpdate.push(rest)
|
||||||
|
|
||||||
|
if (isDefined(values)) {
|
||||||
|
const promotionRule = promotionRulesMap.get(promotionRuleData.id)!
|
||||||
|
|
||||||
|
ruleValueIdsToDelete.push(...promotionRule.values.map((v) => v.id))
|
||||||
|
ruleValuesToCreate.push(
|
||||||
|
...normalizedValues.map((value) => ({
|
||||||
|
value,
|
||||||
|
promotion_rule: promotionRule,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updatedRules] = await Promise.all([
|
||||||
|
this.promotionRuleService_.update(rulesToUpdate, sharedContext),
|
||||||
|
this.promotionRuleValueService_.delete(
|
||||||
|
ruleValueIdsToDelete,
|
||||||
|
sharedContext
|
||||||
|
),
|
||||||
|
this.promotionRuleValueService_.create(ruleValuesToCreate, sharedContext),
|
||||||
|
])
|
||||||
|
|
||||||
|
return updatedRules
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async addPromotionRules(
|
async addPromotionRules(
|
||||||
promotionId: string,
|
promotionId: string,
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ export interface CreatePromotionRuleDTO {
|
|||||||
|
|
||||||
export interface UpdatePromotionRuleDTO {
|
export interface UpdatePromotionRuleDTO {
|
||||||
id: string
|
id: string
|
||||||
|
description?: string | null
|
||||||
|
attribute?: string
|
||||||
|
operator?: PromotionRuleOperatorValues
|
||||||
|
values?: string[] | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemovePromotionRuleDTO {
|
export interface RemovePromotionRuleDTO {
|
||||||
@@ -36,7 +40,6 @@ export interface RemovePromotionRuleDTO {
|
|||||||
export interface FilterablePromotionRuleProps
|
export interface FilterablePromotionRuleProps
|
||||||
extends BaseFilterable<FilterablePromotionRuleProps> {
|
extends BaseFilterable<FilterablePromotionRuleProps> {
|
||||||
id?: string[]
|
id?: string[]
|
||||||
code?: string[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromotionRuleTypes = "buy_rules" | "target_rules" | "rules"
|
export type PromotionRuleTypes = "buy_rules" | "target_rules" | "rules"
|
||||||
@@ -10,9 +10,11 @@ import {
|
|||||||
CreatePromotionRuleDTO,
|
CreatePromotionRuleDTO,
|
||||||
FilterableCampaignProps,
|
FilterableCampaignProps,
|
||||||
FilterablePromotionProps,
|
FilterablePromotionProps,
|
||||||
|
FilterablePromotionRuleProps,
|
||||||
PromotionDTO,
|
PromotionDTO,
|
||||||
PromotionRuleDTO,
|
PromotionRuleDTO,
|
||||||
UpdatePromotionDTO,
|
UpdatePromotionDTO,
|
||||||
|
UpdatePromotionRuleDTO,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
import { CreateCampaignDTO, UpdateCampaignDTO } from "./mutations"
|
import { CreateCampaignDTO, UpdateCampaignDTO } from "./mutations"
|
||||||
|
|
||||||
@@ -134,6 +136,17 @@ export interface IPromotionModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<CampaignDTO>
|
): Promise<CampaignDTO>
|
||||||
|
|
||||||
|
listPromotionRules(
|
||||||
|
filters?: FilterablePromotionRuleProps,
|
||||||
|
config?: FindConfig<PromotionRuleDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionRuleDTO[]>
|
||||||
|
|
||||||
|
updatePromotionRules(
|
||||||
|
data: UpdatePromotionRuleDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionRuleDTO[]>
|
||||||
|
|
||||||
listCampaigns(
|
listCampaigns(
|
||||||
filters?: FilterableCampaignProps,
|
filters?: FilterableCampaignProps,
|
||||||
config?: FindConfig<CampaignDTO>,
|
config?: FindConfig<CampaignDTO>,
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { CreatePromotionRuleDTO, PromotionRuleTypes } from "./common"
|
import {
|
||||||
|
CreatePromotionRuleDTO,
|
||||||
|
PromotionRuleTypes,
|
||||||
|
UpdatePromotionRuleDTO,
|
||||||
|
} from "./common"
|
||||||
|
|
||||||
export type AddPromotionRulesWorkflowDTO = {
|
export type AddPromotionRulesWorkflowDTO = {
|
||||||
rule_type: PromotionRuleTypes
|
rule_type: PromotionRuleTypes
|
||||||
@@ -15,3 +19,7 @@ export type RemovePromotionRulesWorkflowDTO = {
|
|||||||
rule_ids: string[]
|
rule_ids: string[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UpdatePromotionRulesWorkflowDTO = {
|
||||||
|
data: UpdatePromotionRuleDTO[]
|
||||||
|
}
|
||||||
Reference in new issue
Block a user