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:
1 parent
effee5c8bb
commit
5eab9e7399
35 files changed
+1200
-1735
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/promotion": patch
|
||||||
|
"@medusajs/dashboard": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(promotion,dashboard,types,utils,medusa): Add statuses to promotions
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
import { CampaignBudgetType, PromotionType } from "@medusajs/utils"
|
|
||||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
import {
|
||||||
|
CampaignBudgetType,
|
||||||
|
PromotionStatus,
|
||||||
|
PromotionType,
|
||||||
|
} from "@medusajs/utils"
|
||||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
@@ -46,6 +50,7 @@ export const campaignsData = [
|
|||||||
const promotionData = {
|
const promotionData = {
|
||||||
code: "TEST",
|
code: "TEST",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
is_automatic: true,
|
is_automatic: true,
|
||||||
application_method: {
|
application_method: {
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
@@ -108,6 +113,7 @@ medusaIntegrationTestRunner({
|
|||||||
return {
|
return {
|
||||||
code,
|
code,
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
is_automatic: true,
|
is_automatic: true,
|
||||||
application_method: {
|
application_method: {
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
PriceListType,
|
PriceListType,
|
||||||
ProductStatus,
|
ProductStatus,
|
||||||
PromotionRuleOperator,
|
PromotionRuleOperator,
|
||||||
|
PromotionStatus,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
@@ -176,6 +177,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "PROMOTION_APPLIED",
|
code: "PROMOTION_APPLIED",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
@@ -488,6 +490,65 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should remove promotions when promotion is no longer in active state", async () => {
|
||||||
|
let responseBeforePromotionUpdate = await api.post(
|
||||||
|
`/store/carts/${cart.id}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(responseBeforePromotionUpdate.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: cart.id,
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
adjustments: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
code: "PROMOTION_APPLIED",
|
||||||
|
promotion_id: promotion.id,
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/promotions/${promotion.id}`,
|
||||||
|
{ status: PromotionStatus.INACTIVE },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
let responseAfterPromotionUpdate = await api.post(
|
||||||
|
`/store/carts/${cart.id}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(responseAfterPromotionUpdate.status).toEqual(200)
|
||||||
|
expect(responseAfterPromotionUpdate.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: cart.id,
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
adjustments: [],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
adjustments: [],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
describe("with custom shipping options prices", () => {
|
describe("with custom shipping options prices", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
cart = (
|
cart = (
|
||||||
@@ -1205,6 +1266,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "PROMOTION_TEST",
|
code: "PROMOTION_TEST",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PromotionType } from "@medusajs/utils"
|
|
||||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
import { PromotionStatus, PromotionType } from "@medusajs/utils"
|
||||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
@@ -8,6 +8,43 @@ const adminHeaders = {
|
|||||||
headers: { "x-medusa-access-token": "test_token" },
|
headers: { "x-medusa-access-token": "test_token" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const standardPromotionPayload = {
|
||||||
|
code: "TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
|
is_automatic: true,
|
||||||
|
campaign: {
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test-1",
|
||||||
|
budget: {
|
||||||
|
type: "usage",
|
||||||
|
limit: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
application_method: {
|
||||||
|
target_type: "items",
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "each",
|
||||||
|
currency_code: "USD",
|
||||||
|
value: 100,
|
||||||
|
max_quantity: 100,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "test.test",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["test1", "test2"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test.test",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["test1", "test2"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
medusaIntegrationTestRunner({
|
medusaIntegrationTestRunner({
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
describe("Admin Promotions API", () => {
|
describe("Admin Promotions API", () => {
|
||||||
@@ -34,6 +71,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "TEST_ACROSS",
|
code: "TEST_ACROSS",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
allocation: "across",
|
allocation: "across",
|
||||||
@@ -120,6 +158,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "first",
|
code: "first",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "order",
|
target_type: "order",
|
||||||
@@ -183,41 +222,7 @@ medusaIntegrationTestRunner({
|
|||||||
it("should create a standard promotion successfully", async () => {
|
it("should create a standard promotion successfully", async () => {
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/promotions`,
|
`/admin/promotions`,
|
||||||
{
|
standardPromotionPayload,
|
||||||
code: "TEST",
|
|
||||||
type: PromotionType.STANDARD,
|
|
||||||
is_automatic: true,
|
|
||||||
campaign: {
|
|
||||||
name: "test",
|
|
||||||
campaign_identifier: "test-1",
|
|
||||||
budget: {
|
|
||||||
type: "usage",
|
|
||||||
limit: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
application_method: {
|
|
||||||
target_type: "items",
|
|
||||||
type: "fixed",
|
|
||||||
allocation: "each",
|
|
||||||
currency_code: "USD",
|
|
||||||
value: 100,
|
|
||||||
max_quantity: 100,
|
|
||||||
target_rules: [
|
|
||||||
{
|
|
||||||
attribute: "test.test",
|
|
||||||
operator: "eq",
|
|
||||||
values: ["test1", "test2"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
attribute: "test.test",
|
|
||||||
operator: "eq",
|
|
||||||
values: ["test1", "test2"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -275,6 +280,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "TEST",
|
code: "TEST",
|
||||||
type: PromotionType.BUYGET,
|
type: PromotionType.BUYGET,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
is_automatic: true,
|
is_automatic: true,
|
||||||
application_method: {
|
application_method: {
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
@@ -316,6 +322,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "TEST",
|
code: "TEST",
|
||||||
type: PromotionType.BUYGET,
|
type: PromotionType.BUYGET,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
is_automatic: true,
|
is_automatic: true,
|
||||||
application_method: {
|
application_method: {
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
@@ -356,6 +363,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "TEST",
|
code: "TEST",
|
||||||
type: PromotionType.BUYGET,
|
type: PromotionType.BUYGET,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
is_automatic: true,
|
is_automatic: true,
|
||||||
campaign: {
|
campaign: {
|
||||||
name: "test",
|
name: "test",
|
||||||
@@ -457,6 +465,23 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should throw error when an incorrect status is passed", async () => {
|
||||||
|
const { response } = await api
|
||||||
|
.post(
|
||||||
|
`/admin/promotions`,
|
||||||
|
{ ...standardPromotionPayload, status: "does-not-exist" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(400)
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
type: "invalid_data",
|
||||||
|
message:
|
||||||
|
"Invalid request: Expected: 'draft, active, inactive' for field 'status', but got: 'does-not-exist'",
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("DELETE /admin/promotions/:id", () => {
|
describe("DELETE /admin/promotions/:id", () => {
|
||||||
@@ -491,7 +516,7 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
expect(response.status).toEqual(404)
|
expect(response.status).toEqual(404)
|
||||||
expect(response.data.message).toEqual(
|
expect(response.data.message).toEqual(
|
||||||
`Promotion with id "does-not-exist" not found`
|
`Promotion id not found: does-not-exist`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { RemoteLink } from "@medusajs/modules-sdk"
|
import { RemoteLink } from "@medusajs/modules-sdk"
|
||||||
|
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
import { ICartModuleService, IPromotionModuleService } from "@medusajs/types"
|
import { ICartModuleService, IPromotionModuleService } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
Modules,
|
Modules,
|
||||||
|
PromotionStatus,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
|
||||||
import {
|
import {
|
||||||
adminHeaders,
|
adminHeaders,
|
||||||
createAdminUser,
|
createAdminUser,
|
||||||
@@ -48,11 +49,12 @@ medusaIntegrationTestRunner({
|
|||||||
await promotionModuleService.createPromotions({
|
await promotionModuleService.createPromotions({
|
||||||
code: "PROMOTION_APPLIED",
|
code: "PROMOTION_APPLIED",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "300",
|
value: 300,
|
||||||
apply_to_quantity: 1,
|
apply_to_quantity: 1,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
@@ -70,11 +72,12 @@ medusaIntegrationTestRunner({
|
|||||||
await promotionModuleService.createPromotions({
|
await promotionModuleService.createPromotions({
|
||||||
code: "PROMOTION_TEST",
|
code: "PROMOTION_TEST",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
allocation: "across",
|
allocation: "across",
|
||||||
value: "1000",
|
value: 1000,
|
||||||
apply_to_quantity: 1,
|
apply_to_quantity: 1,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
target_rules: [
|
target_rules: [
|
||||||
@@ -165,6 +168,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "PROMOTION_APPLIED",
|
code: "PROMOTION_APPLIED",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "customer_id",
|
attribute: "customer_id",
|
||||||
@@ -181,7 +185,7 @@ medusaIntegrationTestRunner({
|
|||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "shipping_methods",
|
target_type: "shipping_methods",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: 100,
|
||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
target_rules: [
|
target_rules: [
|
||||||
@@ -199,6 +203,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "PROMOTION_NEW",
|
code: "PROMOTION_NEW",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "customer_id",
|
attribute: "customer_id",
|
||||||
@@ -215,7 +220,7 @@ medusaIntegrationTestRunner({
|
|||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "shipping_methods",
|
target_type: "shipping_methods",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "200",
|
value: 200,
|
||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
target_rules: [
|
target_rules: [
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
MedusaError,
|
MedusaError,
|
||||||
Modules,
|
Modules,
|
||||||
ProductStatus,
|
ProductStatus,
|
||||||
|
PromotionStatus,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
@@ -1266,6 +1267,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
code: "TEST",
|
code: "TEST",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
is_automatic: true,
|
is_automatic: true,
|
||||||
campaign: {
|
campaign: {
|
||||||
campaign_identifier: "test",
|
campaign_identifier: "test",
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { RemoteLink } from "@medusajs/modules-sdk"
|
import { RemoteLink } from "@medusajs/modules-sdk"
|
||||||
|
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
import { ICartModuleService, IPromotionModuleService } from "@medusajs/types"
|
import { ICartModuleService, IPromotionModuleService } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
Modules,
|
Modules,
|
||||||
|
PromotionStatus,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
|
||||||
import {
|
import {
|
||||||
adminHeaders,
|
adminHeaders,
|
||||||
createAdminUser,
|
createAdminUser,
|
||||||
@@ -48,11 +49,12 @@ medusaIntegrationTestRunner({
|
|||||||
await promotionModuleService.createPromotions({
|
await promotionModuleService.createPromotions({
|
||||||
code: "PROMOTION_APPLIED",
|
code: "PROMOTION_APPLIED",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "300",
|
value: 300,
|
||||||
apply_to_quantity: 1,
|
apply_to_quantity: 1,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
@@ -70,11 +72,12 @@ medusaIntegrationTestRunner({
|
|||||||
await promotionModuleService.createPromotions({
|
await promotionModuleService.createPromotions({
|
||||||
code: "PROMOTION_APPLIED_TO_REMOVE",
|
code: "PROMOTION_APPLIED_TO_REMOVE",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "items",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "300",
|
value: 300,
|
||||||
apply_to_quantity: 1,
|
apply_to_quantity: 1,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
@@ -180,6 +183,7 @@ medusaIntegrationTestRunner({
|
|||||||
const appliedPromotion =
|
const appliedPromotion =
|
||||||
await promotionModuleService.createPromotions({
|
await promotionModuleService.createPromotions({
|
||||||
code: "PROMOTION_APPLIED",
|
code: "PROMOTION_APPLIED",
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -197,7 +201,7 @@ medusaIntegrationTestRunner({
|
|||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "shipping_methods",
|
target_type: "shipping_methods",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: 100,
|
||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
target_rules: [
|
target_rules: [
|
||||||
@@ -214,6 +218,7 @@ medusaIntegrationTestRunner({
|
|||||||
await promotionModuleService.createPromotions({
|
await promotionModuleService.createPromotions({
|
||||||
code: "PROMOTION_APPLIED_TO_REMOVE",
|
code: "PROMOTION_APPLIED_TO_REMOVE",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "customer_id",
|
attribute: "customer_id",
|
||||||
@@ -230,7 +235,7 @@ medusaIntegrationTestRunner({
|
|||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "shipping_methods",
|
target_type: "shipping_methods",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: 100,
|
||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
target_rules: [
|
target_rules: [
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ const useDynamicSearchResults = (
|
|||||||
{
|
{
|
||||||
q: debouncedSearch,
|
q: debouncedSearch,
|
||||||
limit,
|
limit,
|
||||||
fields: "id,code",
|
fields: "id,code,status",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: isAreaEnabled(currentArea, "promotion"),
|
enabled: isAreaEnabled(currentArea, "promotion"),
|
||||||
|
|||||||
+2
-17
@@ -1,28 +1,13 @@
|
|||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { useTranslation } from "react-i18next"
|
import { getPromotionStatus } from "../../../../../lib/promotions"
|
||||||
import {
|
|
||||||
getPromotionStatus,
|
|
||||||
PromotionStatus,
|
|
||||||
} from "../../../../../lib/promotions"
|
|
||||||
import { StatusCell as StatusCell_ } from "../../common/status-cell"
|
import { StatusCell as StatusCell_ } from "../../common/status-cell"
|
||||||
|
|
||||||
type PromotionCellProps = {
|
type PromotionCellProps = {
|
||||||
promotion: HttpTypes.AdminPromotion
|
promotion: HttpTypes.AdminPromotion
|
||||||
}
|
}
|
||||||
type StatusColors = "grey" | "orange" | "green" | "red"
|
|
||||||
type StatusMap = Record<string, [StatusColors, string]>
|
|
||||||
|
|
||||||
export const StatusCell = ({ promotion }: PromotionCellProps) => {
|
export const StatusCell = ({ promotion }: PromotionCellProps) => {
|
||||||
const { t } = useTranslation()
|
const [color, text] = getPromotionStatus(promotion)
|
||||||
|
|
||||||
const statusMap: StatusMap = {
|
|
||||||
[PromotionStatus.DISABLED]: ["grey", t("statuses.disabled")],
|
|
||||||
[PromotionStatus.ACTIVE]: ["green", t("statuses.active")],
|
|
||||||
[PromotionStatus.SCHEDULED]: ["orange", t("statuses.scheduled")],
|
|
||||||
[PromotionStatus.EXPIRED]: ["red", t("statuses.expired")],
|
|
||||||
}
|
|
||||||
|
|
||||||
const [color, text] = statusMap[getPromotionStatus(promotion)]
|
|
||||||
|
|
||||||
return <StatusCell_ color={color}>{text}</StatusCell_>
|
return <StatusCell_ color={color}>{text}</StatusCell_>
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { FetchError } from "@medusajs/js-sdk"
|
||||||
import { HttpTypes, LinkMethodRequest } from "@medusajs/types"
|
import { HttpTypes, LinkMethodRequest } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
QueryKey,
|
QueryKey,
|
||||||
@@ -10,7 +11,6 @@ import { sdk } from "../../lib/client"
|
|||||||
import { queryClient } from "../../lib/query-client"
|
import { queryClient } from "../../lib/query-client"
|
||||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||||
import { promotionsQueryKeys } from "./promotions"
|
import { promotionsQueryKeys } from "./promotions"
|
||||||
import { FetchError } from "@medusajs/js-sdk"
|
|
||||||
|
|
||||||
const REGIONS_QUERY_KEY = "campaigns" as const
|
const REGIONS_QUERY_KEY = "campaigns" as const
|
||||||
export const campaignsQueryKeys = queryKeysFactory(REGIONS_QUERY_KEY)
|
export const campaignsQueryKeys = queryKeysFactory(REGIONS_QUERY_KEY)
|
||||||
@@ -88,6 +88,8 @@ export const useUpdateCampaign = (
|
|||||||
onSuccess: (data, variables, context) => {
|
onSuccess: (data, variables, context) => {
|
||||||
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.lists() })
|
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.lists() })
|
||||||
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.details() })
|
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.details() })
|
||||||
|
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.lists() })
|
||||||
|
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.details() })
|
||||||
|
|
||||||
options?.onSuccess?.(data, variables, context)
|
options?.onSuccess?.(data, variables, context)
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -1933,7 +1933,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"title": "Status"
|
"label": "Status",
|
||||||
|
"draft": {
|
||||||
|
"title": "Draft",
|
||||||
|
"description": "Customers will not be able to use the code yet"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"title": "Active",
|
||||||
|
"description": "Customers will be able to use the code"
|
||||||
|
},
|
||||||
|
"inactive": {
|
||||||
|
"title": "Inactive",
|
||||||
|
"description": "Customers will no longer be able to use the code"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"method": {
|
"method": {
|
||||||
"label": "Method",
|
"label": "Method",
|
||||||
@@ -2676,6 +2688,8 @@
|
|||||||
"scheduled": "Scheduled",
|
"scheduled": "Scheduled",
|
||||||
"expired": "Expired",
|
"expired": "Expired",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
|
"inactive": "Inactive",
|
||||||
|
"draft": "Draft",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"disabled": "Disabled"
|
"disabled": "Disabled"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,28 @@
|
|||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { i18n } from "../components/utilities/i18n"
|
||||||
|
|
||||||
export enum PromotionStatus {
|
export enum PromotionStatus {
|
||||||
SCHEDULED = "SCHEDULED",
|
SCHEDULED = "SCHEDULED",
|
||||||
EXPIRED = "EXPIRED",
|
EXPIRED = "EXPIRED",
|
||||||
ACTIVE = "ACTIVE",
|
ACTIVE = "ACTIVE",
|
||||||
DISABLED = "DISABLED",
|
INACTIVE = "INACTIVE",
|
||||||
|
DRAFT = "DRAFT",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StatusColors = "grey" | "orange" | "green" | "red" | "grey"
|
||||||
|
export type StatusMap = Record<string, [StatusColors, string]>
|
||||||
|
export const promotionStatusMap: StatusMap = {
|
||||||
|
[PromotionStatus.ACTIVE]: ["green", i18n.t("statuses.active")],
|
||||||
|
[PromotionStatus.INACTIVE]: ["red", i18n.t("statuses.inactive")],
|
||||||
|
[PromotionStatus.DRAFT]: ["grey", i18n.t("statuses.draft")],
|
||||||
|
[PromotionStatus.SCHEDULED]: [
|
||||||
|
"orange",
|
||||||
|
`${i18n.t("promotions.fields.campaign")} ${i18n.t("statuses.scheduled").toLowerCase()}`,
|
||||||
|
],
|
||||||
|
[PromotionStatus.EXPIRED]: [
|
||||||
|
"red",
|
||||||
|
`${i18n.t("promotions.fields.campaign")} ${i18n.t("statuses.expired").toLowerCase()}`,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPromotionStatus = (promotion: HttpTypes.AdminPromotion) => {
|
export const getPromotionStatus = (promotion: HttpTypes.AdminPromotion) => {
|
||||||
@@ -12,11 +30,11 @@ export const getPromotionStatus = (promotion: HttpTypes.AdminPromotion) => {
|
|||||||
const campaign = promotion.campaign
|
const campaign = promotion.campaign
|
||||||
|
|
||||||
if (!campaign) {
|
if (!campaign) {
|
||||||
return PromotionStatus.ACTIVE
|
return promotionStatusMap[promotion.status!.toUpperCase()]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new Date(campaign.starts_at!) > date) {
|
if (campaign.starts_at && new Date(campaign.starts_at!) > date) {
|
||||||
return PromotionStatus.SCHEDULED
|
return promotionStatusMap[PromotionStatus.SCHEDULED]
|
||||||
}
|
}
|
||||||
|
|
||||||
const campaignBudget = campaign.budget
|
const campaignBudget = campaign.budget
|
||||||
@@ -24,8 +42,8 @@ export const getPromotionStatus = (promotion: HttpTypes.AdminPromotion) => {
|
|||||||
campaignBudget && campaignBudget.used! > campaignBudget.limit!
|
campaignBudget && campaignBudget.used! > campaignBudget.limit!
|
||||||
|
|
||||||
if ((campaign.ends_at && new Date(campaign.ends_at) < date) || overBudget) {
|
if ((campaign.ends_at && new Date(campaign.ends_at) < date) || overBudget) {
|
||||||
return PromotionStatus.EXPIRED
|
return promotionStatusMap[PromotionStatus.EXPIRED]
|
||||||
}
|
}
|
||||||
|
|
||||||
return PromotionStatus.ACTIVE
|
return promotionStatusMap[promotion.status!.toUpperCase()]
|
||||||
}
|
}
|
||||||
+51
-8
@@ -1,4 +1,12 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import {
|
||||||
|
ApplicationMethodAllocationValues,
|
||||||
|
ApplicationMethodTargetTypeValues,
|
||||||
|
ApplicationMethodTypeValues,
|
||||||
|
PromotionRuleOperatorValues,
|
||||||
|
PromotionStatusValues,
|
||||||
|
PromotionTypeValues,
|
||||||
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Badge,
|
Badge,
|
||||||
@@ -17,14 +25,6 @@ import { useEffect, useMemo, useState } from "react"
|
|||||||
import { useForm, useWatch } from "react-hook-form"
|
import { useForm, useWatch } from "react-hook-form"
|
||||||
import { Trans, useTranslation } from "react-i18next"
|
import { Trans, useTranslation } from "react-i18next"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import {
|
|
||||||
ApplicationMethodAllocationValues,
|
|
||||||
ApplicationMethodTargetTypeValues,
|
|
||||||
ApplicationMethodTypeValues,
|
|
||||||
PromotionRuleOperatorValues,
|
|
||||||
PromotionTypeValues,
|
|
||||||
} from "@medusajs/types"
|
|
||||||
import { Divider } from "../../../../../components/common/divider"
|
import { Divider } from "../../../../../components/common/divider"
|
||||||
import { Form } from "../../../../../components/common/form"
|
import { Form } from "../../../../../components/common/form"
|
||||||
import { DeprecatedPercentageInput } from "../../../../../components/inputs/percentage-input"
|
import { DeprecatedPercentageInput } from "../../../../../components/inputs/percentage-input"
|
||||||
@@ -50,6 +50,7 @@ const defaultValues = {
|
|||||||
is_automatic: "false",
|
is_automatic: "false",
|
||||||
code: "",
|
code: "",
|
||||||
type: "standard" as PromotionTypeValues,
|
type: "standard" as PromotionTypeValues,
|
||||||
|
status: "draft" as PromotionStatusValues,
|
||||||
rules: [],
|
rules: [],
|
||||||
application_method: {
|
application_method: {
|
||||||
allocation: "each" as ApplicationMethodAllocationValues,
|
allocation: "each" as ApplicationMethodAllocationValues,
|
||||||
@@ -508,6 +509,48 @@ export const CreatePromotionForm = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="status"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("promotions.form.status.label")}
|
||||||
|
</Form.Label>
|
||||||
|
|
||||||
|
<Form.Control>
|
||||||
|
<RadioGroup
|
||||||
|
className="flex gap-y-3"
|
||||||
|
{...field}
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"draft"}
|
||||||
|
label={t("promotions.form.status.draft.title")}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.status.draft.description"
|
||||||
|
)}
|
||||||
|
className={clx("basis-1/2")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"active"}
|
||||||
|
label={t("promotions.form.status.active.title")}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.status.active.description"
|
||||||
|
)}
|
||||||
|
className={clx("basis-1/2")}
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex gap-y-4">
|
<div className="flex gap-y-4">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
+1
@@ -25,6 +25,7 @@ export const CreatePromotionSchema = z
|
|||||||
is_automatic: z.string().toLowerCase(),
|
is_automatic: z.string().toLowerCase(),
|
||||||
code: z.string().min(1),
|
code: z.string().min(1),
|
||||||
type: z.enum(["buyget", "standard"]),
|
type: z.enum(["buyget", "standard"]),
|
||||||
|
status: z.enum(["draft", "active", "inactive"]),
|
||||||
rules: RuleSchema,
|
rules: RuleSchema,
|
||||||
application_method: z.object({
|
application_method: z.object({
|
||||||
allocation: z.enum(["each", "across"]),
|
allocation: z.enum(["each", "across"]),
|
||||||
|
|||||||
+2
-14
@@ -16,10 +16,7 @@ import { ActionMenu } from "../../../../../components/common/action-menu"
|
|||||||
import { useDeletePromotion } from "../../../../../hooks/api/promotions"
|
import { useDeletePromotion } from "../../../../../hooks/api/promotions"
|
||||||
import { formatCurrency } from "../../../../../lib/format-currency"
|
import { formatCurrency } from "../../../../../lib/format-currency"
|
||||||
import { formatPercentage } from "../../../../../lib/percentage-helpers"
|
import { formatPercentage } from "../../../../../lib/percentage-helpers"
|
||||||
import {
|
import { getPromotionStatus } from "../../../../../lib/promotions"
|
||||||
getPromotionStatus,
|
|
||||||
PromotionStatus,
|
|
||||||
} from "../../../../../lib/promotions"
|
|
||||||
|
|
||||||
type PromotionGeneralSectionProps = {
|
type PromotionGeneralSectionProps = {
|
||||||
promotion: HttpTypes.AdminPromotion
|
promotion: HttpTypes.AdminPromotion
|
||||||
@@ -78,16 +75,7 @@ export const PromotionGeneralSection = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const [color, text] = {
|
const [color, text] = getPromotionStatus(promotion)
|
||||||
[PromotionStatus.DISABLED]: ["grey", t("statuses.disabled")],
|
|
||||||
[PromotionStatus.ACTIVE]: ["green", t("statuses.active")],
|
|
||||||
[PromotionStatus.SCHEDULED]: ["orange", t("statuses.scheduled")],
|
|
||||||
[PromotionStatus.EXPIRED]: ["red", t("statuses.expired")],
|
|
||||||
}[getPromotionStatus(promotion)] as [
|
|
||||||
"grey" | "orange" | "green" | "red",
|
|
||||||
string
|
|
||||||
]
|
|
||||||
|
|
||||||
const displayValue = getDisplayValue(promotion)
|
const displayValue = getDisplayValue(promotion)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+50
-2
@@ -1,5 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { PromotionDTO } from "@medusajs/types"
|
import { AdminPromotion } from "@medusajs/types"
|
||||||
import { Button, CurrencyInput, Input, RadioGroup, Text } from "@medusajs/ui"
|
import { Button, CurrencyInput, Input, RadioGroup, Text } from "@medusajs/ui"
|
||||||
import { useForm, useWatch } from "react-hook-form"
|
import { useForm, useWatch } from "react-hook-form"
|
||||||
import { Trans, useTranslation } from "react-i18next"
|
import { Trans, useTranslation } from "react-i18next"
|
||||||
@@ -13,12 +13,13 @@ import { useUpdatePromotion } from "../../../../../hooks/api/promotions"
|
|||||||
import { getCurrencySymbol } from "../../../../../lib/data/currencies"
|
import { getCurrencySymbol } from "../../../../../lib/data/currencies"
|
||||||
|
|
||||||
type EditPromotionFormProps = {
|
type EditPromotionFormProps = {
|
||||||
promotion: PromotionDTO
|
promotion: AdminPromotion
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditPromotionSchema = zod.object({
|
const EditPromotionSchema = zod.object({
|
||||||
is_automatic: zod.string().toLowerCase(),
|
is_automatic: zod.string().toLowerCase(),
|
||||||
code: zod.string().min(1),
|
code: zod.string().min(1),
|
||||||
|
status: zod.enum(["active", "inactive", "draft"]),
|
||||||
value_type: zod.enum(["fixed", "percentage"]),
|
value_type: zod.enum(["fixed", "percentage"]),
|
||||||
value: zod.number(),
|
value: zod.number(),
|
||||||
allocation: zod.enum(["each", "across"]),
|
allocation: zod.enum(["each", "across"]),
|
||||||
@@ -34,6 +35,7 @@ export const EditPromotionDetailsForm = ({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
is_automatic: promotion.is_automatic!.toString(),
|
is_automatic: promotion.is_automatic!.toString(),
|
||||||
code: promotion.code,
|
code: promotion.code,
|
||||||
|
status: promotion.status,
|
||||||
value: promotion.application_method!.value,
|
value: promotion.application_method!.value,
|
||||||
allocation: promotion.application_method!.allocation,
|
allocation: promotion.application_method!.allocation,
|
||||||
value_type: promotion.application_method!.type,
|
value_type: promotion.application_method!.type,
|
||||||
@@ -55,6 +57,7 @@ export const EditPromotionDetailsForm = ({
|
|||||||
{
|
{
|
||||||
is_automatic: data.is_automatic === "true",
|
is_automatic: data.is_automatic === "true",
|
||||||
code: data.code,
|
code: data.code,
|
||||||
|
status: data.status,
|
||||||
application_method: {
|
application_method: {
|
||||||
value: data.value,
|
value: data.value,
|
||||||
type: data.value_type as any,
|
type: data.value_type as any,
|
||||||
@@ -77,6 +80,51 @@ export const EditPromotionDetailsForm = ({
|
|||||||
>
|
>
|
||||||
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-y-auto">
|
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||||
<div className="flex flex-col gap-y-8">
|
<div className="flex flex-col gap-y-8">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="status"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("promotions.form.status.label")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<RadioGroup
|
||||||
|
className="flex-col gap-y-3"
|
||||||
|
{...field}
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"draft"}
|
||||||
|
label={t("promotions.form.status.draft.title")}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.status.draft.description"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"active"}
|
||||||
|
label={t("promotions.form.status.active.title")}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.status.active.description"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
value={"inactive"}
|
||||||
|
label={t("promotions.form.status.inactive.title")}
|
||||||
|
description={t(
|
||||||
|
"promotions.form.status.inactive.description"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="is_automatic"
|
name="is_automatic"
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ export * from "./delete-promotions"
|
|||||||
export * from "./update-campaigns"
|
export * from "./update-campaigns"
|
||||||
export * from "./update-promotion-rules"
|
export * from "./update-promotion-rules"
|
||||||
export * from "./update-promotions"
|
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 {
|
import {
|
||||||
WorkflowData,
|
AdditionalData,
|
||||||
WorkflowResponse,
|
PromotionDTO,
|
||||||
|
PromotionStatusValues,
|
||||||
|
UpdatePromotionDTO,
|
||||||
|
} from "@medusajs/framework/types"
|
||||||
|
import { isString } from "@medusajs/framework/utils"
|
||||||
|
import {
|
||||||
createHook,
|
createHook,
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
|
transform,
|
||||||
|
when,
|
||||||
|
WorkflowData,
|
||||||
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { useRemoteQueryStep } from "../../common"
|
||||||
import { updatePromotionsStep } from "../steps"
|
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.
|
* The data to update one or more promotions, along with custom data that's passed to the workflow's hooks.
|
||||||
@@ -51,7 +61,68 @@ export const updatePromotionsWorkflowId = "update-promotions"
|
|||||||
export const updatePromotionsWorkflow = createWorkflow(
|
export const updatePromotionsWorkflow = createWorkflow(
|
||||||
updatePromotionsWorkflowId,
|
updatePromotionsWorkflowId,
|
||||||
(input: WorkflowData<UpdatePromotionsWorkflowInput>) => {
|
(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", {
|
const promotionsUpdated = createHook("promotionsUpdated", {
|
||||||
promotions: updatedPromotions,
|
promotions: updatedPromotions,
|
||||||
additional_data: input.additional_data,
|
additional_data: input.additional_data,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
ApplicationMethodTargetTypeValues,
|
ApplicationMethodTargetTypeValues,
|
||||||
ApplicationMethodTypeValues,
|
ApplicationMethodTypeValues,
|
||||||
PromotionRuleOperatorValues,
|
PromotionRuleOperatorValues,
|
||||||
|
PromotionStatusValues,
|
||||||
PromotionTypeValues,
|
PromotionTypeValues,
|
||||||
} from "../../../promotion"
|
} from "../../../promotion"
|
||||||
import { AdminCreateCampaign } from "../../campaign"
|
import { AdminCreateCampaign } from "../../campaign"
|
||||||
@@ -64,6 +65,7 @@ export interface AdminUpdatePromotion {
|
|||||||
code?: string
|
code?: string
|
||||||
is_automatic?: boolean
|
is_automatic?: boolean
|
||||||
type?: PromotionTypeValues
|
type?: PromotionTypeValues
|
||||||
|
status?: PromotionStatusValues
|
||||||
campaign_id?: string | null
|
campaign_id?: string | null
|
||||||
campaign?: AdminCreateCampaign
|
campaign?: AdminCreateCampaign
|
||||||
application_method?: AdminUpdateApplicationMethod
|
application_method?: AdminUpdateApplicationMethod
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
ApplicationMethodTargetTypeValues,
|
ApplicationMethodTargetTypeValues,
|
||||||
ApplicationMethodTypeValues,
|
ApplicationMethodTypeValues,
|
||||||
PromotionRuleOperatorValues,
|
PromotionRuleOperatorValues,
|
||||||
|
PromotionStatusValues,
|
||||||
PromotionTypeValues,
|
PromotionTypeValues,
|
||||||
} from "../../promotion"
|
} from "../../promotion"
|
||||||
import { AdminCampaign } from "../campaign"
|
import { AdminCampaign } from "../campaign"
|
||||||
@@ -37,6 +38,7 @@ export interface BasePromotion {
|
|||||||
is_automatic?: boolean
|
is_automatic?: boolean
|
||||||
application_method?: BaseApplicationMethod
|
application_method?: BaseApplicationMethod
|
||||||
rules?: BasePromotionRule[]
|
rules?: BasePromotionRule[]
|
||||||
|
status?: PromotionStatusValues
|
||||||
campaign_id?: string
|
campaign_id?: string
|
||||||
campaign?: AdminCampaign
|
campaign?: AdminCampaign
|
||||||
created_at: string
|
created_at: string
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
CreateApplicationMethodDTO,
|
CreateApplicationMethodDTO,
|
||||||
UpdateApplicationMethodDTO,
|
UpdateApplicationMethodDTO,
|
||||||
} from "./application-method"
|
} from "./application-method"
|
||||||
import { CampaignDTO } from "./campaign"
|
import { CampaignDTO, FilterableCampaignProps } from "./campaign"
|
||||||
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,6 +13,11 @@ import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
|||||||
*/
|
*/
|
||||||
export type PromotionTypeValues = "standard" | "buyget"
|
export type PromotionTypeValues = "standard" | "buyget"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The promotion's possible types.
|
||||||
|
*/
|
||||||
|
export type PromotionStatusValues = "draft" | "active" | "inactive"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The promotion's possible rule types.
|
* The promotion's possible rule types.
|
||||||
*/
|
*/
|
||||||
@@ -41,6 +46,15 @@ export interface PromotionDTO {
|
|||||||
*/
|
*/
|
||||||
type?: PromotionTypeValues
|
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.
|
* Whether the promotion is applied automatically.
|
||||||
*/
|
*/
|
||||||
@@ -85,6 +99,15 @@ export interface CreatePromotionDTO {
|
|||||||
*/
|
*/
|
||||||
type: PromotionTypeValues
|
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.
|
* Whether the promotion is applied automatically.
|
||||||
*/
|
*/
|
||||||
@@ -135,6 +158,15 @@ export interface UpdatePromotionDTO {
|
|||||||
*/
|
*/
|
||||||
type?: PromotionTypeValues
|
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.
|
* The associated application method.
|
||||||
*/
|
*/
|
||||||
@@ -180,4 +212,14 @@ export interface FilterablePromotionProps
|
|||||||
* Filter promotions by their type.
|
* Filter promotions by their type.
|
||||||
*/
|
*/
|
||||||
type?: PromotionTypeValues[]
|
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",
|
BUYGET = "buyget",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PromotionStatus {
|
||||||
|
DRAFT = "draft",
|
||||||
|
ACTIVE = "active",
|
||||||
|
INACTIVE = "inactive",
|
||||||
|
}
|
||||||
|
|
||||||
export enum ApplicationMethodType {
|
export enum ApplicationMethodType {
|
||||||
FIXED = "fixed",
|
FIXED = "fixed",
|
||||||
PERCENTAGE = "percentage",
|
PERCENTAGE = "percentage",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export const defaultAdminPromotionFields = [
|
|||||||
"code",
|
"code",
|
||||||
"is_automatic",
|
"is_automatic",
|
||||||
"type",
|
"type",
|
||||||
|
"status",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
"deleted_at",
|
"deleted_at",
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import {
|
|||||||
ApplicationMethodTargetType,
|
ApplicationMethodTargetType,
|
||||||
ApplicationMethodType,
|
ApplicationMethodType,
|
||||||
PromotionRuleOperator,
|
PromotionRuleOperator,
|
||||||
|
PromotionStatus,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { applyAndAndOrOperators } from "../../utils/common-validators"
|
||||||
import {
|
import {
|
||||||
createFindParams,
|
createFindParams,
|
||||||
createOperatorMap,
|
createOperatorMap,
|
||||||
@@ -13,7 +15,6 @@ import {
|
|||||||
WithAdditionalData,
|
WithAdditionalData,
|
||||||
} from "../../utils/validators"
|
} from "../../utils/validators"
|
||||||
import { CreateCampaign } from "../campaigns/validators"
|
import { CreateCampaign } from "../campaigns/validators"
|
||||||
import { applyAndAndOrOperators } from "../../utils/common-validators"
|
|
||||||
|
|
||||||
export type AdminGetPromotionParamsType = z.infer<
|
export type AdminGetPromotionParamsType = z.infer<
|
||||||
typeof AdminGetPromotionParams
|
typeof AdminGetPromotionParams
|
||||||
@@ -160,6 +161,7 @@ export const CreatePromotion = z
|
|||||||
code: z.string(),
|
code: z.string(),
|
||||||
is_automatic: z.boolean().optional(),
|
is_automatic: z.boolean().optional(),
|
||||||
type: z.nativeEnum(PromotionType),
|
type: z.nativeEnum(PromotionType),
|
||||||
|
status: z.nativeEnum(PromotionStatus).default(PromotionStatus.DRAFT),
|
||||||
campaign_id: z.string().nullish(),
|
campaign_id: z.string().nullish(),
|
||||||
campaign: CreateCampaign.optional(),
|
campaign: CreateCampaign.optional(),
|
||||||
application_method: AdminCreateApplicationMethod,
|
application_method: AdminCreateApplicationMethod,
|
||||||
@@ -183,6 +185,7 @@ export const UpdatePromotion = z
|
|||||||
code: z.string().optional(),
|
code: z.string().optional(),
|
||||||
is_automatic: z.boolean().optional(),
|
is_automatic: z.boolean().optional(),
|
||||||
type: z.nativeEnum(PromotionType).optional(),
|
type: z.nativeEnum(PromotionType).optional(),
|
||||||
|
status: z.nativeEnum(PromotionStatus).optional(),
|
||||||
campaign_id: z.string().nullish(),
|
campaign_id: z.string().nullish(),
|
||||||
application_method: AdminUpdateApplicationMethod.optional(),
|
application_method: AdminUpdateApplicationMethod.optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import {
|
|||||||
IPromotionModuleService,
|
IPromotionModuleService,
|
||||||
PromotionDTO,
|
PromotionDTO,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import { isPresent, toMikroORMEntity } from "@medusajs/framework/utils"
|
import {
|
||||||
|
isPresent,
|
||||||
|
PromotionStatus,
|
||||||
|
toMikroORMEntity,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
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"
|
||||||
@@ -51,6 +55,7 @@ export async function createDefaultPromotion(
|
|||||||
return await service.createPromotions({
|
return await service.createPromotions({
|
||||||
code: "PROMOTION_TEST",
|
code: "PROMOTION_TEST",
|
||||||
type: "standard",
|
type: "standard",
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
campaign_id: "campaign-id-1",
|
campaign_id: "campaign-id-1",
|
||||||
...promotion,
|
...promotion,
|
||||||
application_method: {
|
application_method: {
|
||||||
|
|||||||
+135
-37
@@ -2,6 +2,7 @@ import { IPromotionModuleService } from "@medusajs/framework/types"
|
|||||||
import {
|
import {
|
||||||
ApplicationMethodType,
|
ApplicationMethodType,
|
||||||
Modules,
|
Modules,
|
||||||
|
PromotionStatus,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { moduleIntegrationTestRunner, SuiteOptions } from "@medusajs/test-utils"
|
import { moduleIntegrationTestRunner, SuiteOptions } from "@medusajs/test-utils"
|
||||||
@@ -17,13 +18,85 @@ moduleIntegrationTestRunner({
|
|||||||
service,
|
service,
|
||||||
}: SuiteOptions<IPromotionModuleService>) => {
|
}: SuiteOptions<IPromotionModuleService>) => {
|
||||||
describe("Promotion Service: computeActions", () => {
|
describe("Promotion Service: computeActions", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers()
|
||||||
|
jest.setSystemTime(new Date("02/02/2023"))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createCampaigns(MikroOrmWrapper.forkManager())
|
await createCampaigns(MikroOrmWrapper.forkManager())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should return empty array when promotion is not active (draft or inactive)", async () => {
|
||||||
|
const promotion = await createDefaultPromotion(service, {
|
||||||
|
status: PromotionStatus.DRAFT,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer.customer_group.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["VIP", "top100"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "each",
|
||||||
|
value: 200,
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "product_category.id",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["catg_cotton"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await service.computeActions([promotion.code!], {
|
||||||
|
currency_code: "usd",
|
||||||
|
customer: {
|
||||||
|
customer_group: {
|
||||||
|
id: "VIP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_cotton_tshirt",
|
||||||
|
quantity: 1,
|
||||||
|
subtotal: 100,
|
||||||
|
product_category: {
|
||||||
|
id: "catg_cotton",
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
id: "prod_tshirt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "item_cotton_sweater",
|
||||||
|
quantity: 5,
|
||||||
|
subtotal: 750,
|
||||||
|
product_category: {
|
||||||
|
id: "catg_cotton",
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
id: "prod_sweater",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
describe("when code is not present in database", () => {
|
describe("when code is not present in database", () => {
|
||||||
it("should return empty array when promotion does not exist", async () => {
|
it("should return empty array when promotion does not exist", async () => {
|
||||||
const response = await service.computeActions(["DOES_NOT_EXIST"], {
|
const response = await service.computeActions(["DOES_NOT_EXIST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -87,6 +160,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -136,6 +210,7 @@ moduleIntegrationTestRunner({
|
|||||||
const resultWithoutCustomer = await service.computeActions(
|
const resultWithoutCustomer = await service.computeActions(
|
||||||
["PROMOTION_TEST"],
|
["PROMOTION_TEST"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "item_cotton_tshirt",
|
id: "item_cotton_tshirt",
|
||||||
@@ -221,6 +296,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -257,19 +333,7 @@ moduleIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
action: "addItemAdjustment",
|
action: "addItemAdjustment",
|
||||||
item_id: "item_cotton_tshirt",
|
item_id: "item_cotton_tshirt",
|
||||||
amount: 30,
|
amount: 50,
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: "addItemAdjustment",
|
|
||||||
item_id: "item_cotton_sweater",
|
|
||||||
amount: 30,
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: "addItemAdjustment",
|
|
||||||
item_id: "item_cotton_tshirt",
|
|
||||||
amount: 20,
|
|
||||||
code: "PROMOTION_TEST_2",
|
code: "PROMOTION_TEST_2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -278,6 +342,12 @@ moduleIntegrationTestRunner({
|
|||||||
amount: 50,
|
amount: 50,
|
||||||
code: "PROMOTION_TEST_2",
|
code: "PROMOTION_TEST_2",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_sweater",
|
||||||
|
amount: 30,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -334,6 +404,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -408,6 +479,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -469,6 +541,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -522,6 +595,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -622,6 +696,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -735,6 +810,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -809,6 +885,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -866,6 +943,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -921,6 +999,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -994,6 +1073,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions([], {
|
const result = await service.computeActions([], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1093,6 +1173,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1126,18 +1207,6 @@ moduleIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(JSON.parse(JSON.stringify(result))).toEqual([
|
expect(JSON.parse(JSON.stringify(result))).toEqual([
|
||||||
{
|
|
||||||
action: "addItemAdjustment",
|
|
||||||
item_id: "item_cotton_tshirt",
|
|
||||||
amount: 7.5,
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: "addItemAdjustment",
|
|
||||||
item_id: "item_cotton_sweater",
|
|
||||||
amount: 22.5,
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
action: "addItemAdjustment",
|
action: "addItemAdjustment",
|
||||||
item_id: "item_cotton_tshirt",
|
item_id: "item_cotton_tshirt",
|
||||||
@@ -1150,6 +1219,18 @@ moduleIntegrationTestRunner({
|
|||||||
amount: 37.5,
|
amount: 37.5,
|
||||||
code: "PROMOTION_TEST_2",
|
code: "PROMOTION_TEST_2",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_tshirt",
|
||||||
|
amount: 7.5,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_sweater",
|
||||||
|
amount: 22.5,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1205,6 +1286,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1278,6 +1360,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1334,6 +1417,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1386,6 +1470,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1459,6 +1544,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions([], {
|
const result = await service.computeActions([], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1557,6 +1643,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1668,6 +1755,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1753,6 +1841,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1809,6 +1898,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1864,6 +1954,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -1937,6 +2028,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions([], {
|
const result = await service.computeActions([], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -2012,6 +2104,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
[],
|
[],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -2100,6 +2193,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -2206,6 +2300,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -2279,6 +2374,7 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await service.computeActions(["PROMOTION_TEST"], {
|
const result = await service.computeActions(["PROMOTION_TEST"], {
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -3974,6 +4070,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
@@ -4007,18 +4104,6 @@ moduleIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(JSON.parse(JSON.stringify(result))).toEqual([
|
expect(JSON.parse(JSON.stringify(result))).toEqual([
|
||||||
{
|
|
||||||
action: "addItemAdjustment",
|
|
||||||
item_id: "item_cotton_tshirt",
|
|
||||||
amount: 7.5,
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: "addItemAdjustment",
|
|
||||||
item_id: "item_cotton_sweater",
|
|
||||||
amount: 22.5,
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
action: "addItemAdjustment",
|
action: "addItemAdjustment",
|
||||||
item_id: "item_cotton_tshirt",
|
item_id: "item_cotton_tshirt",
|
||||||
@@ -4031,6 +4116,18 @@ moduleIntegrationTestRunner({
|
|||||||
amount: 37.5,
|
amount: 37.5,
|
||||||
code: "PROMOTION_TEST_2",
|
code: "PROMOTION_TEST_2",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_tshirt",
|
||||||
|
amount: 7.5,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_sweater",
|
||||||
|
amount: 22.5,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -4073,6 +4170,7 @@ moduleIntegrationTestRunner({
|
|||||||
const result = await service.computeActions(
|
const result = await service.computeActions(
|
||||||
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
["PROMOTION_TEST", "PROMOTION_TEST_2"],
|
||||||
{
|
{
|
||||||
|
currency_code: "usd",
|
||||||
customer: {
|
customer: {
|
||||||
customer_group: {
|
customer_group: {
|
||||||
id: "VIP",
|
id: "VIP",
|
||||||
|
|||||||
+16
-5
@@ -6,8 +6,8 @@ import {
|
|||||||
Modules,
|
Modules,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { PromotionModuleService } from "@services"
|
|
||||||
import { SuiteOptions, moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
import { SuiteOptions, moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
import { PromotionModuleService } from "@services"
|
||||||
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
||||||
import {
|
import {
|
||||||
createDefaultPromotion,
|
createDefaultPromotion,
|
||||||
@@ -24,6 +24,15 @@ moduleIntegrationTestRunner({
|
|||||||
service,
|
service,
|
||||||
}: SuiteOptions<IPromotionModuleService>) => {
|
}: SuiteOptions<IPromotionModuleService>) => {
|
||||||
describe("Promotion Service", () => {
|
describe("Promotion Service", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers()
|
||||||
|
jest.setSystemTime(new Date("02/02/2023"))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createCampaigns(MikroOrmWrapper.forkManager())
|
await createCampaigns(MikroOrmWrapper.forkManager())
|
||||||
})
|
})
|
||||||
@@ -809,30 +818,32 @@ moduleIntegrationTestRunner({
|
|||||||
|
|
||||||
expect(count).toEqual(2)
|
expect(count).toEqual(2)
|
||||||
expect(promotions).toEqual([
|
expect(promotions).toEqual([
|
||||||
{
|
expect.objectContaining({
|
||||||
id: "promotion-id-1",
|
id: "promotion-id-1",
|
||||||
code: "PROMOTION_1",
|
code: "PROMOTION_1",
|
||||||
campaign_id: null,
|
campaign_id: null,
|
||||||
campaign: null,
|
campaign: null,
|
||||||
|
status: "draft",
|
||||||
is_automatic: false,
|
is_automatic: false,
|
||||||
type: "standard",
|
type: "standard",
|
||||||
application_method: expect.any(Object),
|
application_method: expect.any(Object),
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
},
|
}),
|
||||||
{
|
expect.objectContaining({
|
||||||
id: "promotion-id-2",
|
id: "promotion-id-2",
|
||||||
code: "PROMOTION_2",
|
code: "PROMOTION_2",
|
||||||
campaign_id: null,
|
campaign_id: null,
|
||||||
campaign: null,
|
campaign: null,
|
||||||
|
status: "draft",
|
||||||
is_automatic: false,
|
is_automatic: false,
|
||||||
type: "standard",
|
type: "standard",
|
||||||
application_method: null,
|
application_method: null,
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
},
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -1,8 +1,8 @@
|
|||||||
import { IPromotionModuleService } from "@medusajs/framework/types"
|
import { IPromotionModuleService } from "@medusajs/framework/types"
|
||||||
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { moduleIntegrationTestRunner, SuiteOptions } from "@medusajs/test-utils"
|
import { moduleIntegrationTestRunner, SuiteOptions } from "@medusajs/test-utils"
|
||||||
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
||||||
import { createDefaultPromotion } from "../../../__fixtures__/promotion"
|
import { createDefaultPromotion } from "../../../__fixtures__/promotion"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
@@ -13,6 +13,15 @@ moduleIntegrationTestRunner({
|
|||||||
service,
|
service,
|
||||||
}: SuiteOptions<IPromotionModuleService>) => {
|
}: SuiteOptions<IPromotionModuleService>) => {
|
||||||
describe("Promotion Service: campaign usage", () => {
|
describe("Promotion Service: campaign usage", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers()
|
||||||
|
jest.setSystemTime(new Date("02/02/2023"))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createCampaigns(MikroOrmWrapper.forkManager())
|
await createCampaigns(MikroOrmWrapper.forkManager())
|
||||||
})
|
})
|
||||||
|
|||||||
+9
@@ -13,6 +13,15 @@ moduleIntegrationTestRunner({
|
|||||||
service,
|
service,
|
||||||
}: SuiteOptions<IPromotionModuleService>) => {
|
}: SuiteOptions<IPromotionModuleService>) => {
|
||||||
describe("Promotion Service: campaign usage", () => {
|
describe("Promotion Service: campaign usage", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers()
|
||||||
|
jest.setSystemTime(new Date("02/02/2023"))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createCampaigns(MikroOrmWrapper.forkManager())
|
await createCampaigns(MikroOrmWrapper.forkManager())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -345,6 +345,21 @@
|
|||||||
],
|
],
|
||||||
"mappedType": "enum"
|
"mappedType": "enum"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"default": "'draft'",
|
||||||
|
"enumItems": [
|
||||||
|
"draft",
|
||||||
|
"active",
|
||||||
|
"inactive"
|
||||||
|
],
|
||||||
|
"mappedType": "enum"
|
||||||
|
},
|
||||||
"campaign_id": {
|
"campaign_id": {
|
||||||
"name": "campaign_id",
|
"name": "campaign_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -414,6 +429,14 @@
|
|||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_type\" ON \"promotion\" (type) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_type\" ON \"promotion\" (type) WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"keyName": "IDX_promotion_status",
|
||||||
|
"columnNames": [],
|
||||||
|
"composite": false,
|
||||||
|
"primary": false,
|
||||||
|
"unique": false,
|
||||||
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_status\" ON \"promotion\" (status) WHERE deleted_at IS NULL"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_promotion_campaign_id",
|
"keyName": "IDX_promotion_campaign_id",
|
||||||
"columnNames": [],
|
"columnNames": [],
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Migration } from "@mikro-orm/migrations"
|
||||||
|
|
||||||
|
export class Migration20250113094144 extends Migration {
|
||||||
|
async up(): Promise<void> {
|
||||||
|
this.addSql(
|
||||||
|
"alter table if exists \"promotion\" add column if not exists \"status\" text check (\"status\" in ('draft', 'active', 'inactive')) not null default 'draft';"
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'CREATE INDEX IF NOT EXISTS "IDX_promotion_status" ON "promotion" (status) WHERE deleted_at IS NULL;'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Data Migration
|
||||||
|
this.addSql(`UPDATE promotion SET status = 'active';`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(): Promise<void> {
|
||||||
|
this.addSql('drop index if exists "IDX_promotion_status";')
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "promotion" drop column if exists "status";'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,10 @@ const Promotion = model
|
|||||||
.index("IDX_promotion_code"),
|
.index("IDX_promotion_code"),
|
||||||
is_automatic: model.boolean().default(false),
|
is_automatic: model.boolean().default(false),
|
||||||
type: model.enum(PromotionUtils.PromotionType).index("IDX_promotion_type"),
|
type: model.enum(PromotionUtils.PromotionType).index("IDX_promotion_type"),
|
||||||
|
status: model
|
||||||
|
.enum(PromotionUtils.PromotionStatus)
|
||||||
|
.index("IDX_promotion_status")
|
||||||
|
.default(PromotionUtils.PromotionStatus.DRAFT),
|
||||||
campaign: model
|
campaign: model
|
||||||
.belongsTo(() => Campaign, {
|
.belongsTo(() => Campaign, {
|
||||||
mappedBy: "promotions",
|
mappedBy: "promotions",
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import {
|
|||||||
CampaignBudgetTypeValues,
|
CampaignBudgetTypeValues,
|
||||||
Context,
|
Context,
|
||||||
DAL,
|
DAL,
|
||||||
|
FilterablePromotionProps,
|
||||||
|
FindConfig,
|
||||||
InferEntityType,
|
InferEntityType,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
ModuleJoinerConfig,
|
ModuleJoinerConfig,
|
||||||
ModulesSdkTypes,
|
ModulesSdkTypes,
|
||||||
|
PromotionDTO,
|
||||||
PromotionTypes,
|
PromotionTypes,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
@@ -24,6 +27,7 @@ import {
|
|||||||
MedusaContext,
|
MedusaContext,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
MedusaService,
|
MedusaService,
|
||||||
|
PromotionStatus,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
toMikroORMEntity,
|
toMikroORMEntity,
|
||||||
transformPropertiesToBigNumber,
|
transformPropertiesToBigNumber,
|
||||||
@@ -134,6 +138,40 @@ export default class PromotionModuleService
|
|||||||
return joinerConfig
|
return joinerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager()
|
||||||
|
listActivePromotions(
|
||||||
|
filters?: FilterablePromotionProps,
|
||||||
|
config?: FindConfig<PromotionDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionDTO[]> {
|
||||||
|
const activeFilters = {
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
|
campaign_id: null,
|
||||||
|
...filters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: PromotionStatus.ACTIVE,
|
||||||
|
...filters,
|
||||||
|
campaign: {
|
||||||
|
...filters?.campaign,
|
||||||
|
$and: [
|
||||||
|
{
|
||||||
|
$or: [{ starts_at: null }, { starts_at: { $lte: new Date() } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$or: [{ ends_at: null }, { ends_at: { $gt: new Date() } }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.listPromotions(activeFilters, config, sharedContext)
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager()
|
@InjectManager()
|
||||||
async registerUsage(
|
async registerUsage(
|
||||||
computedActions: PromotionTypes.UsageComputedActions[],
|
computedActions: PromotionTypes.UsageComputedActions[],
|
||||||
@@ -148,11 +186,9 @@ export default class PromotionModuleService
|
|||||||
const campaignBudgetMap = new Map<string, UpdateCampaignBudgetDTO>()
|
const campaignBudgetMap = new Map<string, UpdateCampaignBudgetDTO>()
|
||||||
const promotionCodeUsageMap = new Map<string, boolean>()
|
const promotionCodeUsageMap = new Map<string, boolean>()
|
||||||
|
|
||||||
const existingPromotions = await this.listPromotions(
|
const existingPromotions = await this.listActivePromotions(
|
||||||
{ code: promotionCodes },
|
{ code: promotionCodes },
|
||||||
{
|
{ relations: ["campaign", "campaign.budget"] },
|
||||||
relations: ["campaign", "campaign.budget"],
|
|
||||||
},
|
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -250,15 +286,13 @@ export default class PromotionModuleService
|
|||||||
const promotionCodeUsageMap = new Map<string, boolean>()
|
const promotionCodeUsageMap = new Map<string, boolean>()
|
||||||
const campaignBudgetMap = new Map<string, UpdateCampaignBudgetDTO>()
|
const campaignBudgetMap = new Map<string, UpdateCampaignBudgetDTO>()
|
||||||
|
|
||||||
const existingPromotions = await this.listPromotions(
|
const existingPromotions = await this.listActivePromotions(
|
||||||
{
|
{
|
||||||
code: computedActions
|
code: computedActions
|
||||||
.map((computedAction) => computedAction.code)
|
.map((computedAction) => computedAction.code)
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
},
|
},
|
||||||
{
|
{ relations: ["campaign", "campaign.budget"] },
|
||||||
relations: ["campaign", "campaign.budget"],
|
|
||||||
},
|
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -362,7 +396,7 @@ export default class PromotionModuleService
|
|||||||
>()
|
>()
|
||||||
const automaticPromotions = preventAutoPromotions
|
const automaticPromotions = preventAutoPromotions
|
||||||
? []
|
? []
|
||||||
: await this.listPromotions(
|
: await this.listActivePromotions(
|
||||||
{ is_automatic: true },
|
{ is_automatic: true },
|
||||||
{ select: ["code"] },
|
{ select: ["code"] },
|
||||||
sharedContext
|
sharedContext
|
||||||
@@ -402,7 +436,7 @@ export default class PromotionModuleService
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const promotions = await this.listPromotions(
|
const promotions = await this.listActivePromotions(
|
||||||
{
|
{
|
||||||
code: [
|
code: [
|
||||||
...promotionCodesToApply,
|
...promotionCodesToApply,
|
||||||
@@ -412,6 +446,7 @@ export default class PromotionModuleService
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
take: null,
|
take: null,
|
||||||
|
order: { application_method: { value: "DESC" } },
|
||||||
relations: [
|
relations: [
|
||||||
"application_method",
|
"application_method",
|
||||||
"application_method.target_rules",
|
"application_method.target_rules",
|
||||||
|
|||||||
Reference in new issue
Block a user