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

This commit is contained in:
Riqwan Thamir authored and GitHub committed 2024-01-05 16:17:22 +01:00
1 parent 7d650771d1
commit dc46ee1189
13 files changed
+1014 -49

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/types": patch
---
feat(types): promotion delete / update / retrieve
@@ -1,3 +1,4 @@
import { CreatePromotionDTO } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { Promotion } from "@models" import { Promotion } from "@models"
import { defaultPromotionsData } from "./data" import { defaultPromotionsData } from "./data"
@@ -6,9 +7,9 @@ export * from "./data"
export async function createPromotions( export async function createPromotions(
manager: SqlEntityManager, manager: SqlEntityManager,
promotionsData = defaultPromotionsData promotionsData: CreatePromotionDTO[] = defaultPromotionsData
): Promise<Promotion[]> { ): Promise<Promotion[]> {
const promotion: Promotion[] = [] const promotions: Promotion[] = []
for (let promotionData of promotionsData) { for (let promotionData of promotionsData) {
let promotion = manager.create(Promotion, promotionData) let promotion = manager.create(Promotion, promotionData)
@@ -18,5 +19,5 @@ export async function createPromotions(
await manager.flush() await manager.flush()
} }
return promotion return promotions
} }
@@ -2,6 +2,7 @@ import { IPromotionModuleService } from "@medusajs/types"
import { PromotionType } from "@medusajs/utils" import { PromotionType } from "@medusajs/utils"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { SqlEntityManager } from "@mikro-orm/postgresql"
import { initialize } from "../../../../src" import { initialize } from "../../../../src"
import { createPromotions } from "../../../__fixtures__/promotion"
import { DB_URL, MikroOrmWrapper } from "../../../utils" import { DB_URL, MikroOrmWrapper } from "../../../utils"
jest.setTimeout(30000) jest.setTimeout(30000)
@@ -70,7 +71,7 @@ describe("Promotion Service", () => {
application_method: { application_method: {
type: "fixed", type: "fixed",
target_type: "order", target_type: "order",
value: 100, value: "100",
}, },
}, },
]) ])
@@ -106,7 +107,7 @@ describe("Promotion Service", () => {
application_method: { application_method: {
type: "fixed", type: "fixed",
target_type: "order", target_type: "order",
value: 100, value: "100",
target_rules: [ target_rules: [
{ {
attribute: "product_id", attribute: "product_id",
@@ -164,7 +165,7 @@ describe("Promotion Service", () => {
application_method: { application_method: {
type: "fixed", type: "fixed",
target_type: "item", target_type: "item",
value: 100, value: "100",
}, },
}, },
]) ])
@@ -185,7 +186,7 @@ describe("Promotion Service", () => {
type: "fixed", type: "fixed",
allocation: "each", allocation: "each",
target_type: "shipping", target_type: "shipping",
value: 100, value: "100",
}, },
}, },
]) ])
@@ -347,4 +348,541 @@ describe("Promotion Service", () => {
) )
}) })
}) })
describe("update", () => {
it("should throw an error when required params are not passed", async () => {
const error = await service
.update([
{
type: PromotionType.STANDARD,
} as any,
])
.catch((e) => e)
expect(error.message).toContain('Promotion with id "undefined" not found')
})
it("should update the attributes of a promotion successfully", async () => {
await createPromotions(repositoryManager)
const [updatedPromotion] = await service.update([
{
id: "promotion-id-1",
is_automatic: true,
code: "TEST",
type: PromotionType.BUYGET,
},
])
expect(updatedPromotion).toEqual(
expect.objectContaining({
is_automatic: true,
code: "TEST",
type: PromotionType.BUYGET,
})
)
})
it("should update the attributes of a application method successfully", async () => {
const [createdPromotion] = await service.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "item",
allocation: "across",
value: "100",
},
},
])
const applicationMethod = createdPromotion.application_method
const [updatedPromotion] = await service.update([
{
id: createdPromotion.id,
application_method: {
id: applicationMethod?.id as string,
value: "200",
},
},
])
expect(updatedPromotion).toEqual(
expect.objectContaining({
application_method: expect.objectContaining({
value: 200,
}),
})
)
})
it("should change max_quantity to 0 when target_type is changed to order", async () => {
const [createdPromotion] = await service.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "item",
allocation: "each",
value: "100",
max_quantity: 500,
},
},
])
const applicationMethod = createdPromotion.application_method
const [updatedPromotion] = await service.update([
{
id: createdPromotion.id,
application_method: {
id: applicationMethod?.id as string,
target_type: "order",
allocation: "across",
},
},
])
expect(updatedPromotion).toEqual(
expect.objectContaining({
application_method: expect.objectContaining({
target_type: "order",
allocation: "across",
max_quantity: 0,
}),
})
)
})
it("should validate the attributes of a application method successfully", async () => {
const [createdPromotion] = await service.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
allocation: "across",
value: "100",
},
},
])
const applicationMethod = createdPromotion.application_method
let error = await service
.update([
{
id: createdPromotion.id,
application_method: {
id: applicationMethod?.id as string,
target_type: "should-error",
} as any,
},
])
.catch((e) => e)
expect(error.message).toContain(
`application_method.target_type should be one of order, shipping, item`
)
error = await service
.update([
{
id: createdPromotion.id,
application_method: {
id: applicationMethod?.id as string,
allocation: "should-error",
} as any,
},
])
.catch((e) => e)
expect(error.message).toContain(
`application_method.allocation should be one of each, across`
)
error = await service
.update([
{
id: createdPromotion.id,
application_method: {
id: applicationMethod?.id as string,
type: "should-error",
} as any,
},
])
.catch((e) => e)
expect(error.message).toContain(
`application_method.type should be one of fixed, percentage`
)
})
})
describe("retrieve", () => {
beforeEach(async () => {
await createPromotions(repositoryManager)
})
const id = "promotion-id-1"
it("should return promotion for the given id", async () => {
const promotion = await service.retrieve(id)
expect(promotion).toEqual(
expect.objectContaining({
id,
})
)
})
it("should throw an error when promotion with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"Promotion with id: does-not-exist was not found"
)
})
it("should throw an error when a id is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
expect(error.message).toEqual('"promotionId" must be defined')
})
it("should return promotion based on config select param", async () => {
const promotion = await service.retrieve(id, {
select: ["id"],
})
const serialized = JSON.parse(JSON.stringify(promotion))
expect(serialized).toEqual({
id,
})
})
})
describe("delete", () => {
beforeEach(async () => {
await createPromotions(repositoryManager)
})
const id = "promotion-id-1"
it("should delete the promotions given an id successfully", async () => {
await service.delete([id])
const promotions = await service.list({
id: [id],
})
expect(promotions).toHaveLength(0)
})
})
describe("addPromotionRules", () => {
let promotion
beforeEach(async () => {
;[promotion] = await service.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "item",
allocation: "each",
value: "100",
max_quantity: 500,
},
},
])
})
it("should throw an error when promotion with id does not exist", async () => {
let error
try {
await service.addPromotionRules("does-not-exist", [])
} catch (e) {
error = e
}
expect(error.message).toEqual(
"Promotion with id: does-not-exist was not found"
)
})
it("should throw an error when a id is not provided", async () => {
let error
try {
await service.addPromotionRules(undefined as unknown as string, [])
} catch (e) {
error = e
}
expect(error.message).toEqual('"promotionId" must be defined')
})
it("should successfully create rules for a promotion", async () => {
promotion = await service.addPromotionRules(promotion.id, [
{
attribute: "customer_group_id",
operator: "in",
values: ["VIP", "top100"],
},
])
expect(promotion).toEqual(
expect.objectContaining({
id: promotion.id,
rules: [
expect.objectContaining({
attribute: "customer_group_id",
operator: "in",
values: [
expect.objectContaining({ value: "VIP" }),
expect.objectContaining({ value: "top100" }),
],
}),
],
})
)
})
})
describe("addPromotionTargetRules", () => {
let promotion
beforeEach(async () => {
;[promotion] = await service.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "item",
allocation: "each",
value: "100",
max_quantity: 500,
},
},
])
})
it("should throw an error when promotion with id does not exist", async () => {
let error
try {
await service.addPromotionTargetRules("does-not-exist", [])
} catch (e) {
error = e
}
expect(error.message).toEqual(
"Promotion with id: does-not-exist was not found"
)
})
it("should throw an error when a id is not provided", async () => {
let error
try {
await service.addPromotionTargetRules(
undefined as unknown as string,
[]
)
} catch (e) {
error = e
}
expect(error.message).toEqual('"promotionId" must be defined')
})
it("should successfully create target rules for a promotion", async () => {
promotion = await service.addPromotionTargetRules(promotion.id, [
{
attribute: "customer_group_id",
operator: "in",
values: ["VIP", "top100"],
},
])
expect(promotion).toEqual(
expect.objectContaining({
id: promotion.id,
application_method: expect.objectContaining({
target_rules: [
expect.objectContaining({
attribute: "customer_group_id",
operator: "in",
values: [
expect.objectContaining({ value: "VIP" }),
expect.objectContaining({ value: "top100" }),
],
}),
],
}),
})
)
})
})
describe("removePromotionRules", () => {
let promotion
beforeEach(async () => {
;[promotion] = await service.create([
{
code: "TEST",
type: PromotionType.STANDARD,
rules: [
{
attribute: "customer_group_id",
operator: "in",
values: ["VIP", "top100"],
},
],
application_method: {
type: "fixed",
target_type: "item",
allocation: "each",
value: "100",
max_quantity: 500,
},
},
])
})
it("should throw an error when promotion with id does not exist", async () => {
let error
try {
await service.removePromotionRules("does-not-exist", [])
} catch (e) {
error = e
}
expect(error.message).toEqual(
"Promotion with id: does-not-exist was not found"
)
})
it("should throw an error when a id is not provided", async () => {
let error
try {
await service.removePromotionRules(undefined as unknown as string, [])
} catch (e) {
error = e
}
expect(error.message).toEqual('"promotionId" must be defined')
})
it("should successfully create rules for a promotion", async () => {
const [ruleId] = promotion.rules.map((rule) => rule.id)
promotion = await service.removePromotionRules(promotion.id, [
{ id: ruleId },
])
expect(promotion).toEqual(
expect.objectContaining({
id: promotion.id,
rules: [],
})
)
})
})
describe("removePromotionTargetRules", () => {
let promotion
beforeEach(async () => {
;[promotion] = await service.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "item",
allocation: "each",
value: "100",
max_quantity: 500,
target_rules: [
{
attribute: "customer_group_id",
operator: "in",
values: ["VIP", "top100"],
},
],
},
},
])
})
it("should throw an error when promotion with id does not exist", async () => {
let error
try {
await service.removePromotionTargetRules("does-not-exist", [])
} catch (e) {
error = e
}
expect(error.message).toEqual(
"Promotion with id: does-not-exist was not found"
)
})
it("should throw an error when a id is not provided", async () => {
let error
try {
await service.removePromotionTargetRules(
undefined as unknown as string,
[]
)
} catch (e) {
error = e
}
expect(error.message).toEqual('"promotionId" must be defined')
})
it("should successfully create rules for a promotion", async () => {
const [ruleId] = promotion.application_method.target_rules.map(
(rule) => rule.id
)
promotion = await service.removePromotionTargetRules(promotion.id, [
{ id: ruleId },
])
expect(promotion).toEqual(
expect.objectContaining({
id: promotion.id,
application_method: expect.objectContaining({
target_rules: [],
}),
})
)
})
})
}) })
@@ -1,7 +1,7 @@
import { import {
ApplicationMethodAllocation, ApplicationMethodAllocationValues,
ApplicationMethodTargetType, ApplicationMethodTargetTypeValues,
ApplicationMethodType, ApplicationMethodTypeValues,
} from "@medusajs/types" } from "@medusajs/types"
import { PromotionUtils, generateEntityId } from "@medusajs/utils" import { PromotionUtils, generateEntityId } from "@medusajs/utils"
import { import {
@@ -27,6 +27,7 @@ type OptionalFields =
| "created_at" | "created_at"
| "updated_at" | "updated_at"
| "deleted_at" | "deleted_at"
@Entity() @Entity()
export default class ApplicationMethod { export default class ApplicationMethod {
[OptionalProps]?: OptionalFields [OptionalProps]?: OptionalFields
@@ -35,25 +36,25 @@ export default class ApplicationMethod {
id!: string id!: string
@Property({ columnType: "numeric", nullable: true, serializer: Number }) @Property({ columnType: "numeric", nullable: true, serializer: Number })
value?: number | null value?: string | null
@Property({ columnType: "numeric", nullable: true, serializer: Number }) @Property({ columnType: "numeric", nullable: true, serializer: Number })
max_quantity?: number | null max_quantity?: number | null
@Index({ name: "IDX_application_method_type" }) @Index({ name: "IDX_application_method_type" })
@Enum(() => PromotionUtils.ApplicationMethodType) @Enum(() => PromotionUtils.ApplicationMethodType)
type: ApplicationMethodType type: ApplicationMethodTypeValues
@Index({ name: "IDX_application_method_target_type" }) @Index({ name: "IDX_application_method_target_type" })
@Enum(() => PromotionUtils.ApplicationMethodTargetType) @Enum(() => PromotionUtils.ApplicationMethodTargetType)
target_type: ApplicationMethodTargetType target_type: ApplicationMethodTargetTypeValues
@Index({ name: "IDX_application_method_allocation" }) @Index({ name: "IDX_application_method_allocation" })
@Enum({ @Enum({
items: () => PromotionUtils.ApplicationMethodAllocation, items: () => PromotionUtils.ApplicationMethodAllocation,
nullable: true, nullable: true,
}) })
allocation?: ApplicationMethodAllocation allocation?: ApplicationMethodAllocationValues
@OneToOne({ @OneToOne({
entity: () => Promotion, entity: () => Promotion,
@@ -48,6 +48,7 @@ export default class Promotion {
@OneToOne({ @OneToOne({
entity: () => ApplicationMethod, entity: () => ApplicationMethod,
mappedBy: (am) => am.promotion, mappedBy: (am) => am.promotion,
cascade: ["soft-remove"] as any,
}) })
application_method: ApplicationMethod application_method: ApplicationMethod
@@ -10,6 +10,7 @@ import {
InjectManager, InjectManager,
InjectTransactionManager, InjectTransactionManager,
MedusaContext, MedusaContext,
MedusaError,
} from "@medusajs/utils" } from "@medusajs/utils"
import { ApplicationMethod, Promotion } from "@models" import { ApplicationMethod, Promotion } from "@models"
import { import {
@@ -19,8 +20,14 @@ import {
PromotionService, PromotionService,
} from "@services" } from "@services"
import { joinerConfig } from "../joiner-config" import { joinerConfig } from "../joiner-config"
import { CreateApplicationMethodDTO, CreatePromotionDTO } from "../types"
import { import {
CreateApplicationMethodDTO,
CreatePromotionDTO,
UpdateApplicationMethodDTO,
UpdatePromotionDTO,
} from "../types"
import {
allowedAllocationForQuantity,
validateApplicationMethodAttributes, validateApplicationMethodAttributes,
validatePromotionRuleAttributes, validatePromotionRuleAttributes,
} from "../utils" } from "../utils"
@@ -64,6 +71,26 @@ export default class PromotionModuleService<
return joinerConfig 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_") @InjectManager("baseRepository_")
async list( async list(
filters: PromotionTypes.FilterablePromotionProps = {}, filters: PromotionTypes.FilterablePromotionProps = {},
@@ -76,7 +103,7 @@ export default class PromotionModuleService<
sharedContext sharedContext
) )
return this.baseRepository_.serialize<PromotionTypes.PromotionDTO[]>( return await this.baseRepository_.serialize<PromotionTypes.PromotionDTO[]>(
promotions, promotions,
{ {
populate: true, populate: true,
@@ -94,7 +121,13 @@ export default class PromotionModuleService<
return await this.list( return await this.list(
{ id: promotions.map((p) => p!.id) }, { 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 sharedContext
) )
@@ -195,6 +228,162 @@ export default class PromotionModuleService<
return createdPromotions 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( protected async createPromotionRulesAndValues(
rulesData: PromotionTypes.CreatePromotionRuleDTO[], rulesData: PromotionTypes.CreatePromotionRuleDTO[],
relationName: "promotions" | "application_methods", relationName: "promotions" | "application_methods",
@@ -224,4 +413,111 @@ export default class PromotionModuleService<
await this.promotionRuleValueService_.create(promotionRuleValuesData) 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 { import {
ApplicationMethodAllocation, ApplicationMethodAllocationValues,
ApplicationMethodTargetType, ApplicationMethodTargetTypeValues,
ApplicationMethodType, ApplicationMethodTypeValues,
PromotionDTO, PromotionDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { Promotion } from "@models"
export interface CreateApplicationMethodDTO { export interface CreateApplicationMethodDTO {
type: ApplicationMethodType type: ApplicationMethodTypeValues
target_type: ApplicationMethodTargetType target_type: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocation allocation?: ApplicationMethodAllocationValues
value?: number value?: string | null
promotion: PromotionDTO | string promotion: Promotion | string | PromotionDTO
max_quantity?: number max_quantity?: number | null
} }
export interface UpdateApplicationMethodDTO { export interface UpdateApplicationMethodDTO {
id: string 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 { export interface UpdatePromotionDTO {
id: string 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 { import {
ApplicationMethodAllocation, ApplicationMethodAllocation,
ApplicationMethodTargetType, ApplicationMethodTargetType,
ApplicationMethodType,
MedusaError, MedusaError,
isDefined, isDefined,
} from "@medusajs/utils" } from "@medusajs/utils"
import { CreateApplicationMethodDTO } from "../../types"
const allowedTargetTypes: string[] = [ export const allowedAllocationTargetTypes: string[] = [
ApplicationMethodTargetType.SHIPPING, ApplicationMethodTargetType.SHIPPING,
ApplicationMethodTargetType.ITEM, ApplicationMethodTargetType.ITEM,
] ]
const allowedAllocationTypes: string[] = [ export const allowedAllocationTypes: string[] = [
ApplicationMethodAllocation.ACROSS, ApplicationMethodAllocation.ACROSS,
ApplicationMethodAllocation.EACH, ApplicationMethodAllocation.EACH,
] ]
const allowedAllocationForQuantity: string[] = [ export const allowedAllocationForQuantity: string[] = [
ApplicationMethodAllocation.EACH, ApplicationMethodAllocation.EACH,
] ]
export function validateApplicationMethodAttributes( export function validateApplicationMethodAttributes(data: {
data: CreateApplicationMethodDTO 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 ( if (
allowedTargetTypes.includes(data.target_type) && allowedAllocationTargetTypes.includes(data.target_type) &&
!allowedAllocationTypes.includes(data.allocation || "") !allowedAllocationTypes.includes(data.allocation || "")
) { ) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
`application_method.allocation should be either '${allowedAllocationTypes.join( `application_method.allocation should be either '${allowedAllocationTypes.join(
" OR " " OR "
)}' when application_method.target_type is either '${allowedTargetTypes.join( )}' when application_method.target_type is either '${allowedAllocationTargetTypes.join(
" OR " " 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 ( if (
allowedAllocationForQuantity.includes(data.allocation || "") && data.allocation &&
allowedAllocationForQuantity.includes(data.allocation) &&
!isDefined(data.max_quantity) !isDefined(data.max_quantity)
) { ) {
throw new MedusaError( throw new MedusaError(
@@ -1,33 +1,46 @@
import { BaseFilterable } from "../../dal" import { BaseFilterable } from "../../dal"
import { PromotionDTO } from "./promotion" import { PromotionDTO } from "./promotion"
import { CreatePromotionRuleDTO } from "./promotion-rule" import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
export type ApplicationMethodType = "fixed" | "percentage" export type ApplicationMethodTypeValues = "fixed" | "percentage"
export type ApplicationMethodTargetType = "order" | "shipping" | "item" export type ApplicationMethodTargetTypeValues = "order" | "shipping" | "item"
export type ApplicationMethodAllocation = "each" | "across" export type ApplicationMethodAllocationValues = "each" | "across"
export interface ApplicationMethodDTO { export interface ApplicationMethodDTO {
id: string id: string
type?: ApplicationMethodTypeValues
target_type?: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocationValues
value?: string | null
max_quantity?: number | null
promotion?: PromotionDTO | string
target_rules?: PromotionRuleDTO[]
} }
export interface CreateApplicationMethodDTO { export interface CreateApplicationMethodDTO {
type: ApplicationMethodType type: ApplicationMethodTypeValues
target_type: ApplicationMethodTargetType target_type: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocation allocation?: ApplicationMethodAllocationValues
value?: number value?: string | null
max_quantity?: number max_quantity?: number | null
promotion?: PromotionDTO | string promotion?: PromotionDTO | string
target_rules?: CreatePromotionRuleDTO[] target_rules?: CreatePromotionRuleDTO[]
} }
export interface UpdateApplicationMethodDTO { export interface UpdateApplicationMethodDTO {
id: string id: string
type?: ApplicationMethodTypeValues
target_type?: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocationValues
value?: string | null
max_quantity?: number | null
promotion?: PromotionDTO | string
} }
export interface FilterableApplicationMethodProps export interface FilterableApplicationMethodProps
extends BaseFilterable<FilterableApplicationMethodProps> { extends BaseFilterable<FilterableApplicationMethodProps> {
id?: string[] id?: string[]
type?: ApplicationMethodType[] type?: ApplicationMethodTypeValues[]
target_type?: ApplicationMethodTargetType[] target_type?: ApplicationMethodTargetTypeValues[]
allocation?: ApplicationMethodAllocation[] allocation?: ApplicationMethodAllocationValues[]
} }
@@ -24,6 +24,10 @@ export interface UpdatePromotionRuleDTO {
id: string id: string
} }
export interface RemovePromotionRuleDTO {
id: string
}
export interface FilterablePromotionRuleProps export interface FilterablePromotionRuleProps
extends BaseFilterable<FilterablePromotionRuleProps> { extends BaseFilterable<FilterablePromotionRuleProps> {
id?: string[] id?: string[]
@@ -1,11 +1,19 @@
import { BaseFilterable } from "../../dal" import { BaseFilterable } from "../../dal"
import { CreateApplicationMethodDTO } from "./application-method" import {
ApplicationMethodDTO,
CreateApplicationMethodDTO,
UpdateApplicationMethodDTO,
} from "./application-method"
import { CreatePromotionRuleDTO } from "./promotion-rule" import { CreatePromotionRuleDTO } from "./promotion-rule"
export type PromotionType = "standard" | "buyget" export type PromotionType = "standard" | "buyget"
export interface PromotionDTO { export interface PromotionDTO {
id: string id: string
code?: string
type?: PromotionType
is_automatic?: boolean
application_method?: ApplicationMethodDTO
} }
export interface CreatePromotionDTO { export interface CreatePromotionDTO {
@@ -18,6 +26,10 @@ export interface CreatePromotionDTO {
export interface UpdatePromotionDTO { export interface UpdatePromotionDTO {
id: string id: string
is_automatic?: boolean
code?: string
type?: PromotionType
application_method?: UpdateApplicationMethodDTO
} }
export interface FilterablePromotionProps export interface FilterablePromotionProps
+40
View File
@@ -3,8 +3,11 @@ import { IModuleService } from "../modules-sdk"
import { Context } from "../shared-context" import { Context } from "../shared-context"
import { import {
CreatePromotionDTO, CreatePromotionDTO,
CreatePromotionRuleDTO,
FilterablePromotionProps, FilterablePromotionProps,
PromotionDTO, PromotionDTO,
RemovePromotionRuleDTO,
UpdatePromotionDTO,
} from "./common" } from "./common"
export interface IPromotionModuleService extends IModuleService { export interface IPromotionModuleService extends IModuleService {
@@ -13,9 +16,46 @@ export interface IPromotionModuleService extends IModuleService {
sharedContext?: Context sharedContext?: Context
): Promise<PromotionDTO[]> ): Promise<PromotionDTO[]>
update(
data: UpdatePromotionDTO[],
sharedContext?: Context
): Promise<PromotionDTO[]>
list( list(
filters?: FilterablePromotionProps, filters?: FilterablePromotionProps,
config?: FindConfig<PromotionDTO>, config?: FindConfig<PromotionDTO>,
sharedContext?: Context sharedContext?: Context
): Promise<PromotionDTO[]> ): Promise<PromotionDTO[]>
retrieve(
id: string,
config?: FindConfig<PromotionDTO>,
sharedContext?: Context
): Promise<PromotionDTO>
delete(ids: string[], sharedContext?: Context): Promise<void>
addPromotionRules(
promotionId: string,
rulesData: CreatePromotionRuleDTO[],
sharedContext?: Context
): Promise<PromotionDTO>
addPromotionTargetRules(
promotionId: string,
rulesData: CreatePromotionRuleDTO[],
sharedContext?: Context
): Promise<PromotionDTO>
removePromotionRules(
promotionId: string,
rulesData: RemovePromotionRuleDTO[],
sharedContext?: Context
): Promise<PromotionDTO>
removePromotionTargetRules(
promotionId: string,
rulesData: RemovePromotionRuleDTO[],
sharedContext?: Context
): Promise<PromotionDTO>
} }