feat(types): promotion delete / update / retrieve / add/remove-rules (#5988)

This commit is contained in:
Riqwan Thamir
2024-01-05 16:17:22 +01:00
committed by GitHub
parent 7d650771d1
commit dc46ee1189
13 changed files with 1014 additions and 49 deletions
@@ -1,7 +1,7 @@
import {
ApplicationMethodAllocation,
ApplicationMethodTargetType,
ApplicationMethodType,
ApplicationMethodAllocationValues,
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
} from "@medusajs/types"
import { PromotionUtils, generateEntityId } from "@medusajs/utils"
import {
@@ -27,6 +27,7 @@ type OptionalFields =
| "created_at"
| "updated_at"
| "deleted_at"
@Entity()
export default class ApplicationMethod {
[OptionalProps]?: OptionalFields
@@ -35,25 +36,25 @@ export default class ApplicationMethod {
id!: string
@Property({ columnType: "numeric", nullable: true, serializer: Number })
value?: number | null
value?: string | null
@Property({ columnType: "numeric", nullable: true, serializer: Number })
max_quantity?: number | null
@Index({ name: "IDX_application_method_type" })
@Enum(() => PromotionUtils.ApplicationMethodType)
type: ApplicationMethodType
type: ApplicationMethodTypeValues
@Index({ name: "IDX_application_method_target_type" })
@Enum(() => PromotionUtils.ApplicationMethodTargetType)
target_type: ApplicationMethodTargetType
target_type: ApplicationMethodTargetTypeValues
@Index({ name: "IDX_application_method_allocation" })
@Enum({
items: () => PromotionUtils.ApplicationMethodAllocation,
nullable: true,
})
allocation?: ApplicationMethodAllocation
allocation?: ApplicationMethodAllocationValues
@OneToOne({
entity: () => Promotion,
@@ -48,6 +48,7 @@ export default class Promotion {
@OneToOne({
entity: () => ApplicationMethod,
mappedBy: (am) => am.promotion,
cascade: ["soft-remove"] as any,
})
application_method: ApplicationMethod
@@ -10,6 +10,7 @@ import {
InjectManager,
InjectTransactionManager,
MedusaContext,
MedusaError,
} from "@medusajs/utils"
import { ApplicationMethod, Promotion } from "@models"
import {
@@ -19,8 +20,14 @@ import {
PromotionService,
} from "@services"
import { joinerConfig } from "../joiner-config"
import { CreateApplicationMethodDTO, CreatePromotionDTO } from "../types"
import {
CreateApplicationMethodDTO,
CreatePromotionDTO,
UpdateApplicationMethodDTO,
UpdatePromotionDTO,
} from "../types"
import {
allowedAllocationForQuantity,
validateApplicationMethodAttributes,
validatePromotionRuleAttributes,
} from "../utils"
@@ -64,6 +71,26 @@ export default class PromotionModuleService<
return joinerConfig
}
@InjectManager("baseRepository_")
async retrieve(
id: string,
config: FindConfig<PromotionTypes.PromotionDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PromotionTypes.PromotionDTO> {
const promotion = await this.promotionService_.retrieve(
id,
config,
sharedContext
)
return await this.baseRepository_.serialize<PromotionTypes.PromotionDTO>(
promotion,
{
populate: true,
}
)
}
@InjectManager("baseRepository_")
async list(
filters: PromotionTypes.FilterablePromotionProps = {},
@@ -76,7 +103,7 @@ export default class PromotionModuleService<
sharedContext
)
return this.baseRepository_.serialize<PromotionTypes.PromotionDTO[]>(
return await this.baseRepository_.serialize<PromotionTypes.PromotionDTO[]>(
promotions,
{
populate: true,
@@ -94,7 +121,13 @@ export default class PromotionModuleService<
return await this.list(
{ id: promotions.map((p) => p!.id) },
{
relations: ["application_method", "rules", "rules.values"],
relations: [
"application_method",
"application_method.target_rules",
"application_method.target_rules.values",
"rules",
"rules.values",
],
},
sharedContext
)
@@ -195,6 +228,162 @@ export default class PromotionModuleService<
return createdPromotions
}
@InjectManager("baseRepository_")
async update(
data: PromotionTypes.UpdatePromotionDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PromotionTypes.PromotionDTO[]> {
const promotions = await this.update_(data, sharedContext)
return await this.list(
{ id: promotions.map((p) => p!.id) },
{
relations: [
"application_method",
"application_method.target_rules",
"rules",
"rules.values",
],
},
sharedContext
)
}
@InjectTransactionManager("baseRepository_")
protected async update_(
data: PromotionTypes.UpdatePromotionDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const promotionIds = data.map((d) => d.id)
const existingPromotions = await this.promotionService_.list(
{
id: promotionIds,
},
{
relations: ["application_method"],
}
)
const existingPromotionsMap = new Map<string, Promotion>(
existingPromotions.map((promotion) => [promotion.id, promotion])
)
const promotionsData: UpdatePromotionDTO[] = []
const applicationMethodsData: UpdateApplicationMethodDTO[] = []
for (const {
application_method: applicationMethodData,
...promotionData
} of data) {
promotionsData.push(promotionData)
if (!applicationMethodData) {
continue
}
const existingPromotion = existingPromotionsMap.get(promotionData.id)
const existingApplicationMethod = existingPromotion?.application_method
if (!existingApplicationMethod) {
continue
}
if (
applicationMethodData.allocation &&
!allowedAllocationForQuantity.includes(applicationMethodData.allocation)
) {
applicationMethodData.max_quantity = null
}
validateApplicationMethodAttributes({
type: applicationMethodData.type || existingApplicationMethod.type,
target_type:
applicationMethodData.target_type ||
existingApplicationMethod.target_type,
allocation:
applicationMethodData.allocation ||
existingApplicationMethod.allocation,
max_quantity:
applicationMethodData.max_quantity ||
existingApplicationMethod.max_quantity,
})
applicationMethodsData.push(applicationMethodData)
}
const updatedPromotions = this.promotionService_.update(
promotionsData,
sharedContext
)
if (applicationMethodsData.length) {
await this.applicationMethodService_.update(
applicationMethodsData,
sharedContext
)
}
return updatedPromotions
}
@InjectManager("baseRepository_")
@InjectTransactionManager("baseRepository_")
async addPromotionRules(
promotionId: string,
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PromotionTypes.PromotionDTO> {
const promotion = await this.promotionService_.retrieve(promotionId)
await this.createPromotionRulesAndValues(
rulesData,
"promotions",
promotion,
sharedContext
)
return this.retrieve(promotionId, {
relations: ["rules", "rules.values"],
})
}
@InjectManager("baseRepository_")
@InjectTransactionManager("baseRepository_")
async addPromotionTargetRules(
promotionId: string,
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PromotionTypes.PromotionDTO> {
const promotion = await this.promotionService_.retrieve(promotionId, {
relations: ["application_method"],
})
const applicationMethod = promotion.application_method
if (!applicationMethod) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`application_method for promotion not found`
)
}
await this.createPromotionRulesAndValues(
rulesData,
"application_methods",
applicationMethod,
sharedContext
)
return this.retrieve(promotionId, {
relations: [
"rules",
"rules.values",
"application_method",
"application_method.target_rules",
"application_method.target_rules.values",
],
})
}
protected async createPromotionRulesAndValues(
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
relationName: "promotions" | "application_methods",
@@ -224,4 +413,111 @@ export default class PromotionModuleService<
await this.promotionRuleValueService_.create(promotionRuleValuesData)
}
}
@InjectTransactionManager("baseRepository_")
async delete(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.promotionService_.delete(ids, sharedContext)
}
@InjectManager("baseRepository_")
async removePromotionRules(
promotionId: string,
rulesData: PromotionTypes.RemovePromotionRuleDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PromotionTypes.PromotionDTO> {
await this.removePromotionRules_(promotionId, rulesData, sharedContext)
return this.retrieve(
promotionId,
{ relations: ["rules", "rules.values"] },
sharedContext
)
}
@InjectTransactionManager("baseRepository_")
protected async removePromotionRules_(
promotionId: string,
rulesData: PromotionTypes.RemovePromotionRuleDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
const promotionRuleIdsToRemove = rulesData.map((ruleData) => ruleData.id)
const promotion = await this.promotionService_.retrieve(
promotionId,
{ relations: ["rules"] },
sharedContext
)
const existingPromotionRuleIds = promotion.rules
.toArray()
.map((rule) => rule.id)
const idsToRemove = promotionRuleIdsToRemove.filter((ruleId) =>
existingPromotionRuleIds.includes(ruleId)
)
await this.promotionRuleService_.delete(idsToRemove, sharedContext)
}
@InjectManager("baseRepository_")
async removePromotionTargetRules(
promotionId: string,
rulesData: PromotionTypes.RemovePromotionRuleDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PromotionTypes.PromotionDTO> {
await this.removePromotionTargetRules_(
promotionId,
rulesData,
sharedContext
)
return this.retrieve(
promotionId,
{
relations: [
"rules",
"rules.values",
"application_method",
"application_method.target_rules",
"application_method.target_rules.values",
],
},
sharedContext
)
}
@InjectTransactionManager("baseRepository_")
protected async removePromotionTargetRules_(
promotionId: string,
rulesData: PromotionTypes.RemovePromotionRuleDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
const promotionRuleIds = rulesData.map((ruleData) => ruleData.id)
const promotion = await this.promotionService_.retrieve(
promotionId,
{ relations: ["application_method.target_rules"] },
sharedContext
)
const applicationMethod = promotion.application_method
if (!applicationMethod) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`application_method for promotion not found`
)
}
const targetRuleIdsToRemove = applicationMethod.target_rules
.toArray()
.filter((rule) => promotionRuleIds.includes(rule.id))
.map((rule) => rule.id)
await this.promotionRuleService_.delete(
targetRuleIdsToRemove,
sharedContext
)
}
}
@@ -1,19 +1,27 @@
import {
ApplicationMethodAllocation,
ApplicationMethodTargetType,
ApplicationMethodType,
ApplicationMethodAllocationValues,
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
PromotionDTO,
} from "@medusajs/types"
import { Promotion } from "@models"
export interface CreateApplicationMethodDTO {
type: ApplicationMethodType
target_type: ApplicationMethodTargetType
allocation?: ApplicationMethodAllocation
value?: number
promotion: PromotionDTO | string
max_quantity?: number
type: ApplicationMethodTypeValues
target_type: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocationValues
value?: string | null
promotion: Promotion | string | PromotionDTO
max_quantity?: number | null
}
export interface UpdateApplicationMethodDTO {
id: string
type?: ApplicationMethodTypeValues
target_type?: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocationValues
value?: string | null
promotion?: Promotion | string | PromotionDTO
max_quantity?: number | null
}
@@ -8,4 +8,8 @@ export interface CreatePromotionDTO {
export interface UpdatePromotionDTO {
id: string
code?: string
// TODO: add this when buyget is available
// type: PromotionType
is_automatic?: boolean
}
@@ -1,44 +1,86 @@
import {
ApplicationMethodAllocationValues,
ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues,
} from "@medusajs/types"
import {
ApplicationMethodAllocation,
ApplicationMethodTargetType,
ApplicationMethodType,
MedusaError,
isDefined,
} from "@medusajs/utils"
import { CreateApplicationMethodDTO } from "../../types"
const allowedTargetTypes: string[] = [
export const allowedAllocationTargetTypes: string[] = [
ApplicationMethodTargetType.SHIPPING,
ApplicationMethodTargetType.ITEM,
]
const allowedAllocationTypes: string[] = [
export const allowedAllocationTypes: string[] = [
ApplicationMethodAllocation.ACROSS,
ApplicationMethodAllocation.EACH,
]
const allowedAllocationForQuantity: string[] = [
export const allowedAllocationForQuantity: string[] = [
ApplicationMethodAllocation.EACH,
]
export function validateApplicationMethodAttributes(
data: CreateApplicationMethodDTO
) {
export function validateApplicationMethodAttributes(data: {
type: ApplicationMethodTypeValues
target_type: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocationValues
max_quantity?: number | null
}) {
const allTargetTypes: string[] = Object.values(ApplicationMethodTargetType)
if (!allTargetTypes.includes(data.target_type)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`application_method.target_type should be one of ${allTargetTypes.join(
", "
)}`
)
}
const allTypes: string[] = Object.values(ApplicationMethodType)
if (!allTypes.includes(data.type)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`application_method.type should be one of ${allTypes.join(", ")}`
)
}
if (
allowedTargetTypes.includes(data.target_type) &&
allowedAllocationTargetTypes.includes(data.target_type) &&
!allowedAllocationTypes.includes(data.allocation || "")
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`application_method.allocation should be either '${allowedAllocationTypes.join(
" OR "
)}' when application_method.target_type is either '${allowedTargetTypes.join(
)}' when application_method.target_type is either '${allowedAllocationTargetTypes.join(
" OR "
)}'`
)
}
const allAllocationTypes: string[] = Object.values(
ApplicationMethodAllocation
)
if (data.allocation && !allAllocationTypes.includes(data.allocation)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`application_method.allocation should be one of ${allAllocationTypes.join(
", "
)}`
)
}
if (
allowedAllocationForQuantity.includes(data.allocation || "") &&
data.allocation &&
allowedAllocationForQuantity.includes(data.allocation) &&
!isDefined(data.max_quantity)
) {
throw new MedusaError(