feat(promotion,dashboard,types,utils,medusa): Add statuses to promotions (#10950)

what:

- adds a status column to promotion table
- introduce active promotion query
- scope revert, register and compute actions to active promotions
- admin to create and update promotion with statuses

RESOLVES CMRC-845
RESOLVES CMRC-846
RESOLVES CMRC-847
RESOLVES CMRC-848
RESOLVES CMRC-849
RESOLVES CMRC-850
This commit is contained in:
Riqwan Thamir authored and GitHub committed 2025-01-16 19:17:22 +00:00
1 parent effee5c8bb
commit 5eab9e7399
35 files changed
+1208 -1743

No files matched your search

@@ -9,3 +9,4 @@ export * from "./delete-promotions"
export * from "./update-campaigns"
export * from "./update-promotion-rules"
export * from "./update-promotions"
export * from "./update-promotions-status"
@@ -0,0 +1,55 @@
import {
AdditionalData,
PromotionStatusValues,
} from "@medusajs/framework/types"
import { MedusaError, PromotionStatus } from "@medusajs/framework/utils"
import {
WorkflowResponse,
createHook,
createStep,
createWorkflow,
} from "@medusajs/framework/workflows-sdk"
import { updatePromotionsStep } from "../steps"
export type UpdatePromotionsStatusWorkflowInput = {
promotionsData: {
id: string
status: PromotionStatusValues
}[]
} & AdditionalData
export const updatePromotionsValidationStep = createStep(
"update-promotions-validation",
async function ({ promotionsData }: UpdatePromotionsStatusWorkflowInput) {
for (const promotionData of promotionsData) {
const allowedStatuses: PromotionStatusValues[] =
Object.values(PromotionStatus)
if (!allowedStatuses.includes(promotionData.status)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`promotion's status should be one of - ${allowedStatuses.join(", ")}`
)
}
}
}
)
export const updatePromotionsStatusWorkflowId = "update-promotions-status"
export const updatePromotionsStatusWorkflow = createWorkflow(
updatePromotionsStatusWorkflowId,
(input: UpdatePromotionsStatusWorkflowInput) => {
updatePromotionsValidationStep(input)
const updatedPromotions = updatePromotionsStep(input.promotionsData)
const promotionStatusUpdated = createHook("promotionStatusUpdated", {
promotions: updatedPromotions,
additional_data: input.additional_data,
})
return new WorkflowResponse(updatedPromotions, {
hooks: [promotionStatusUpdated],
})
}
)
@@ -1,11 +1,21 @@
import { AdditionalData, UpdatePromotionDTO } from "@medusajs/framework/types"
import {
WorkflowData,
WorkflowResponse,
AdditionalData,
PromotionDTO,
PromotionStatusValues,
UpdatePromotionDTO,
} from "@medusajs/framework/types"
import { isString } from "@medusajs/framework/utils"
import {
createHook,
createWorkflow,
transform,
when,
WorkflowData,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import { useRemoteQueryStep } from "../../common"
import { updatePromotionsStep } from "../steps"
import { updatePromotionsStatusWorkflow } from "./update-promotions-status"
/**
* The data to update one or more promotions, along with custom data that's passed to the workflow's hooks.
@@ -20,12 +30,12 @@ export type UpdatePromotionsWorkflowInput = {
export const updatePromotionsWorkflowId = "update-promotions"
/**
* This workflow updates one or more promotions. It's used by the [Update Promotion Admin API Route](https://docs.medusajs.com/api/admin#promotions_postpromotionsid).
*
*
* This workflow has a hook that allows you to perform custom actions on the updated promotion. For example, you can pass under `additional_data` custom data that
* allows you to update custom data models linked to the promotions.
*
*
* You can also use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around updating promotions.
*
*
* @example
* const { result } = await updatePromotionsWorkflow(container)
* .run({
@@ -41,17 +51,78 @@ export const updatePromotionsWorkflowId = "update-promotions"
* }
* }
* })
*
*
* @summary
*
*
* Update one or more promotions.
*
*
* @property hooks.promotionsUpdated - This hook is executed after the promotions are updated. You can consume this hook to perform custom actions on the updated promotions.
*/
export const updatePromotionsWorkflow = createWorkflow(
updatePromotionsWorkflowId,
(input: WorkflowData<UpdatePromotionsWorkflowInput>) => {
const updatedPromotions = updatePromotionsStep(input.promotionsData)
const promotionIds = transform({ input }, ({ input }) =>
input.promotionsData.map((pd) => pd.id)
)
const promotions = useRemoteQueryStep({
entry_point: "promotion",
variables: { id: promotionIds },
fields: ["id", "status"],
list: true,
throw_if_key_not_found: true,
}).config({ name: "get-promotions" })
const promotionInputs = transform(
{ promotions, input },
({ promotions, input }) => {
const promotionMap: Record<string, PromotionDTO> = {}
const promotionsUpdateInput: UpdatePromotionsWorkflowInput["promotionsData"] =
[]
const promotionsStatusUpdateInput: {
id: string
status: PromotionStatusValues
}[] = []
for (const promotion of promotions) {
promotionMap[promotion.id] = promotion
}
for (const promotionUpdateData of input.promotionsData) {
const promotion = promotionMap[promotionUpdateData.id]
const { status, ...rest } = promotionUpdateData
promotionsUpdateInput.push(rest)
if (
isString(status) &&
promotionUpdateData.status !== promotion.status
) {
promotionsStatusUpdateInput.push({
id: promotionUpdateData.id,
status,
})
}
}
return { promotionsUpdateInput, promotionsStatusUpdateInput }
}
)
const updatedPromotions = updatePromotionsStep(
promotionInputs.promotionsUpdateInput
)
when({ promotionInputs }, ({ promotionInputs }) => {
return !!promotionInputs.promotionsStatusUpdateInput?.length
}).then(() => {
updatePromotionsStatusWorkflow.runAsStep({
input: {
promotionsData: promotionInputs.promotionsStatusUpdateInput,
},
})
})
const promotionsUpdated = createHook("promotionsUpdated", {
promotions: updatedPromotions,
additional_data: input.additional_data,
@@ -3,6 +3,7 @@ import {
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
PromotionRuleOperatorValues,
PromotionStatusValues,
PromotionTypeValues,
} from "../../../promotion"
import { AdminCreateCampaign } from "../../campaign"
@@ -64,6 +65,7 @@ export interface AdminUpdatePromotion {
code?: string
is_automatic?: boolean
type?: PromotionTypeValues
status?: PromotionStatusValues
campaign_id?: string | null
campaign?: AdminCreateCampaign
application_method?: AdminUpdateApplicationMethod
@@ -3,6 +3,7 @@ import {
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
PromotionRuleOperatorValues,
PromotionStatusValues,
PromotionTypeValues,
} from "../../promotion"
import { AdminCampaign } from "../campaign"
@@ -37,6 +38,7 @@ export interface BasePromotion {
is_automatic?: boolean
application_method?: BaseApplicationMethod
rules?: BasePromotionRule[]
status?: PromotionStatusValues
campaign_id?: string
campaign?: AdminCampaign
created_at: string
@@ -5,7 +5,7 @@ import {
CreateApplicationMethodDTO,
UpdateApplicationMethodDTO,
} from "./application-method"
import { CampaignDTO } from "./campaign"
import { CampaignDTO, FilterableCampaignProps } from "./campaign"
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
/**
@@ -13,6 +13,11 @@ import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
*/
export type PromotionTypeValues = "standard" | "buyget"
/**
* The promotion's possible types.
*/
export type PromotionStatusValues = "draft" | "active" | "inactive"
/**
* The promotion's possible rule types.
*/
@@ -41,6 +46,15 @@ export interface PromotionDTO {
*/
type?: PromotionTypeValues
/**
* The status of the promotion:
*
* - `active` promotion is available for user to consume
* - `inactive` promotion is no longer available to the user
* - `draft` promotion is currently being prepared
*/
status?: PromotionStatusValues
/**
* Whether the promotion is applied automatically.
*/
@@ -85,6 +99,15 @@ export interface CreatePromotionDTO {
*/
type: PromotionTypeValues
/**
* The status of the promotion:
*
* - `draft` indicates that a promotion is currently being prepared
* - `active` indicates that a promotion is active
* - `inactive` indicates that a promotion is no longer active
*/
status: PromotionStatusValues
/**
* Whether the promotion is applied automatically.
*/
@@ -135,6 +158,15 @@ export interface UpdatePromotionDTO {
*/
type?: PromotionTypeValues
/**
* The status of the promotion:
*
* - `draft` indicates that a promotion is currently being prepared
* - `active` indicates that a promotion is active
* - `inactive` indicates that a promotion is no longer active
*/
status?: PromotionStatusValues
/**
* The associated application method.
*/
@@ -180,4 +212,14 @@ export interface FilterablePromotionProps
* Filter promotions by their type.
*/
type?: PromotionTypeValues[]
/**
* Filter promotions by their status.
*/
status?: PromotionStatusValues[]
/**
* Filter promotions by their campaign
*/
campaign?: FilterableCampaignProps
}
@@ -3,6 +3,12 @@ export enum PromotionType {
BUYGET = "buyget",
}
export enum PromotionStatus {
DRAFT = "draft",
ACTIVE = "active",
INACTIVE = "inactive",
}
export enum ApplicationMethodType {
FIXED = "fixed",
PERCENTAGE = "percentage",