feat: Completely revamp the pricing module (#7852)

* feat: Completely revamp the pricing module

* chore: Update all places to the new pricing interfaces

* fix: Remove unnecessary join to itself

* chore: Add data migration for existing users

* fix: Apply the correct index to price rule
This commit is contained in:
Stevche Radevski
2024-07-01 09:47:03 +02:00
committed by GitHub
parent 1f360a3245
commit c661180c44
84 changed files with 661 additions and 3725 deletions

View File

@@ -92,22 +92,6 @@ medusaIntegrationTestRunner({
adminHeaders
)
).data.price_list
// BREAKING: You need to register rule types before you can use them
await api.post(
"/admin/pricing/rule-types",
{ name: "Region ID", rule_attribute: "region_id", default_priority: 0 },
adminHeaders
)
await api.post(
"/admin/pricing/rule-types",
{
name: "Customer Group ID",
rule_attribute: "customer_group_id",
default_priority: 0,
},
adminHeaders
)
})
describe("/admin/price-lists", () => {

View File

@@ -808,12 +808,6 @@ medusaIntegrationTestRunner({
// },
// async () => {
// const variantId = baseProduct.variants[0].id
// await pricingService.createRuleTypes([
// {
// name: "Region ID",
// rule_attribute: "region_id",
// },
// ])
// const priceSet = await createVariantPriceSet({
// container,
// variantId,
@@ -1228,16 +1222,6 @@ medusaIntegrationTestRunner({
})
it("creates a product variant with price rules", async () => {
await api.post(
`/admin/pricing/rule-types`,
{
name: "Region",
rule_attribute: "region_id",
default_priority: 1,
},
adminHeaders
)
const response = await api.post(
"/admin/products",
{

View File

@@ -96,12 +96,6 @@ medusaIntegrationTestRunner({
describe("updates a variant's default prices (ignores prices associated with a Price List)", () => {
it("successfully updates a variant's default prices by changing an existing price (currency_code)", async () => {
await api.post(
`/admin/pricing/rule-types`,
{ name: "Region", rule_attribute: "region_id", default_priority: 1 },
adminHeaders
)
const data = {
prices: [
{

View File

@@ -10,7 +10,6 @@ import {
createAdminUser,
} from "../../../../helpers/create-admin-user"
import { getProductFixture } from "../../../../helpers/fixtures"
import { createDefaultRuleTypes } from "../../../../modules/helpers/create-default-rule-types"
jest.setTimeout(30000)
@@ -83,7 +82,6 @@ medusaIntegrationTestRunner({
beforeEach(async () => {
appContainer = getContainer()
await createAdminUser(dbConnection, adminHeaders, appContainer)
await createDefaultRuleTypes(appContainer)
const storeModule: IStoreModuleService = appContainer.resolve(
ModuleRegistrationName.STORE

View File

@@ -46,16 +46,8 @@ medusaIntegrationTestRunner({
},
])
await pricingModule.createRuleTypes([
{
name: "customer_group_id",
rule_attribute: "customer_group_id",
},
])
const [priceSet1, priceSet2] = await pricingModule.createPriceSets([
{
rules: [{ rule_attribute: "customer_group_id" }],
prices: [
{
amount: 3000,
@@ -71,7 +63,6 @@ medusaIntegrationTestRunner({
],
},
{
rules: [{ rule_attribute: "customer_group_id" }],
prices: [
{
amount: 400,

View File

@@ -6,7 +6,6 @@ import {
simpleRegionFactory,
} from "../../../../factories"
import { createAdminUser } from "../../../../helpers/create-admin-user"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
jest.setTimeout(50000)
@@ -37,7 +36,6 @@ medusaIntegrationTestRunner({
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, appContainer)
await createDefaultRuleTypes(appContainer)
await simpleRegionFactory(dbConnection, {
id: "test-region",

View File

@@ -5,7 +5,6 @@ import {
import { IPricingModuleService } from "@medusajs/types"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
import { createAdminUser } from "../../../../helpers/create-admin-user"
@@ -39,7 +38,6 @@ medusaIntegrationTestRunner({
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, appContainer)
await createDefaultRuleTypes(appContainer)
await simpleRegionFactory(dbConnection, {
id: "test-region",

View File

@@ -5,7 +5,6 @@ import {
import { IPricingModuleService } from "@medusajs/types"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
import { createAdminUser } from "../../../../helpers/create-admin-user"
@@ -37,7 +36,6 @@ medusaIntegrationTestRunner({
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, appContainer)
await createDefaultRuleTypes(appContainer)
await simpleRegionFactory(dbConnection, {
id: "test-region",

View File

@@ -5,7 +5,6 @@ import {
import { IPricingModuleService } from "@medusajs/types"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
import { createAdminUser } from "../../../../helpers/create-admin-user"
@@ -37,7 +36,6 @@ medusaIntegrationTestRunner({
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, appContainer)
await createDefaultRuleTypes(appContainer)
await simpleRegionFactory(dbConnection, {
id: "test-region",

View File

@@ -66,11 +66,6 @@ medusaIntegrationTestRunner({
variant = product.variants[0]
variant2 = product.variants[1]
await pricingModule.createRuleTypes([
{ name: "Customer Group ID", rule_attribute: "customer_group_id" },
{ name: "Region ID", rule_attribute: "region_id" },
])
})
describe("GET /admin/price-lists", () => {

View File

@@ -7,7 +7,6 @@ import {
simpleRegionFactory,
} from "../../../../factories"
import { createAdminUser } from "../../../../helpers/create-admin-user"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
jest.setTimeout(50000)
@@ -37,7 +36,6 @@ medusaIntegrationTestRunner({
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, appContainer)
await createDefaultRuleTypes(appContainer)
await simpleRegionFactory(dbConnection, {
id: "test-region",

View File

@@ -1,228 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService, RuleTypeDTO } from "@medusajs/types"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createAdminUser } from "../../../../helpers/create-admin-user"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe("Admin: Pricing Rule Types API", () => {
let appContainer
let pricingModule: IPricingModuleService
let ruleTypes: RuleTypeDTO[]
beforeAll(async () => {
appContainer = getContainer()
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
})
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, appContainer)
ruleTypes = await pricingModule.createRuleTypes([
{ name: "Customer Group ID", rule_attribute: "customer_group_id" },
{ name: "Region ID", rule_attribute: "region_id" },
])
})
describe("GET /admin/pricing", () => {
it("should get all rule types and its prices with rules", async () => {
let response = await api.get(
`/admin/pricing/rule-types`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(2)
expect(response.data.rule_types).toEqual([
expect.objectContaining({ id: expect.any(String) }),
expect.objectContaining({ id: expect.any(String) }),
])
response = await api.get(
`/admin/pricing/rule-types?fields=id,rule_attribute,created_at`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(2)
expect(response.data.rule_types).toEqual(
expect.arrayContaining([
{
id: ruleTypes[0].id,
rule_attribute: ruleTypes[0].rule_attribute,
created_at: expect.any(String),
},
{
id: ruleTypes[1].id,
rule_attribute: ruleTypes[1].rule_attribute,
created_at: expect.any(String),
},
])
)
})
})
describe("GET /admin/pricing/:id", () => {
it("should retrieve a rule type and its prices with rules", async () => {
const ruleType = ruleTypes[0]
let response = await api.get(
`/admin/pricing/rule-types/${ruleType.id}`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.rule_type).toEqual(
expect.objectContaining({
id: ruleType.id,
})
)
response = await api.get(
`/admin/pricing/rule-types/${ruleType.id}?fields=id,created_at`,
adminHeaders
)
expect(response.data.rule_type).toEqual({
id: ruleType.id,
created_at: expect.any(String),
})
})
it("should throw an error when rule type is not found", async () => {
const error = await api
.get(`/admin/pricing/rule-types/does-not-exist`, adminHeaders)
.catch((e) => e)
expect(error.response.status).toBe(404)
expect(error.response.data).toEqual({
type: "not_found",
message: "RuleType with id: does-not-exist was not found",
})
})
})
describe("POST /admin/pricing/rule-types", () => {
it("should throw an error if required params are not passed", async () => {
const { response } = await api
.post(
`/admin/pricing/rule-types`,
{
rule_attribute: "rule_attr_test1",
default_priority: 7,
},
adminHeaders
)
.catch((e) => e)
expect(response.status).toEqual(400)
// expect(response.data.message).toEqual(
// "name must be a string, name should not be empty"
// )
})
it("should create a rule type successfully", async () => {
const response = await api.post(
`/admin/pricing/rule-types`,
{
name: "test",
rule_attribute: "rule_attr_test",
default_priority: 6,
},
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.rule_type).toEqual(
expect.objectContaining({
id: expect.any(String),
name: "test",
rule_attribute: "rule_attr_test",
default_priority: 6,
})
)
})
})
describe("POST /admin/pricing/rule-types/:id", () => {
it("should throw an error if id does not exist", async () => {
const { response } = await api
.post(`/admin/pricing/rule-types/does-not-exist`, {}, adminHeaders)
.catch((e) => e)
expect(response.status).toEqual(404)
expect(response.data.message).toEqual(
`RuleType with id "does-not-exist" not found`
)
})
it("should update a rule type successfully", async () => {
const [ruleType] = ruleTypes
const response = await api.post(
`/admin/pricing/rule-types/${ruleType.id}`,
{
name: "test update",
rule_attribute: "test_update",
default_priority: 7,
},
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.rule_type).toEqual(
expect.objectContaining({
id: expect.any(String),
name: "test update",
rule_attribute: "test_update",
default_priority: 7,
})
)
})
})
describe("DELETE /admin/pricing/rule-types/:id", () => {
it("should delete rule type successfully", async () => {
const [ruleType] = ruleTypes
const response = await api.delete(
`/admin/pricing/rule-types/${ruleType.id}`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
id: ruleType.id,
object: "rule_type",
deleted: true,
})
const deletedRuleTypes = await pricingModule.listRuleTypes({
id: [ruleType.id],
})
expect(deletedRuleTypes.length).toEqual(0)
})
it("should return 200 when id does not exist", async () => {
const response = await api.delete(
`/admin/pricing/rule-types/does-not-exist`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
id: "does-not-exist",
object: "rule_type",
deleted: true,
})
})
})
})
},
})

View File

@@ -1,7 +1,6 @@
import { simpleCartFactory, simpleRegionFactory } from "../../../factories"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { createDefaultRuleTypes } from "../../helpers/create-default-rule-types"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createAdminUser } from "../../../helpers/create-admin-user"
@@ -33,7 +32,6 @@ medusaIntegrationTestRunner({
medusaContainer = getContainer()
})
beforeEach(async () => {
await createDefaultRuleTypes(medusaContainer)
await createAdminUser(dbConnection, adminHeaders, medusaContainer)
await simpleRegionFactory(dbConnection, {
id: "region-1",
@@ -78,12 +76,7 @@ medusaIntegrationTestRunner({
productId = response.data.product.id
const variant = response.data.product.variants[0]
ruleType = await pricingModuleService.createRuleTypes([
{ name: "region_id", rule_attribute: "region_id" },
])
priceSet = await pricingModuleService.createPriceSets({
rules: [{ rule_attribute: "region_id" }],
prices: [
{
amount: 1000,

View File

@@ -6,7 +6,6 @@ import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import { simpleSalesChannelFactory } from "../../../../factories"
import productSeeder from "../../../../helpers/product-seeder"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import {
adminHeaders,
createAdminUser,
@@ -65,7 +64,6 @@ describe.skip("Batch job of product-export type", () => {
beforeEach(async () => {
const container = getContainer()
await createDefaultRuleTypes(container)
await productSeeder(dbConnection)
await createAdminUser(dbConnection, adminHeaders, container)
await userSeeder(dbConnection)

View File

@@ -8,7 +8,6 @@ import { initDb, useDb } from "../../../../environment-helpers/use-db"
import { simpleProductFactory } from "../../../../factories"
import { simpleProductCollectionFactory } from "../../../../factories/simple-product-collection-factory"
import batchJobSeeder from "../../../../helpers/batch-job-seeder"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import {
adminHeaders,
createAdminUser,
@@ -99,7 +98,6 @@ describe.skip("Product import batch job", () => {
beforeEach(async () => {
const container = getContainer()
await createDefaultRuleTypes(container)
await batchJobSeeder(dbConnection)
await createAdminUser(dbConnection, adminHeaders, container)
await userSeeder(dbConnection)

View File

@@ -1,18 +0,0 @@
import { IPricingModuleService } from "@medusajs/types"
export const createDefaultRuleTypes = async (container) => {
const pricingModuleService: IPricingModuleService = container.resolve(
"pricingModuleService"
)
return await pricingModuleService.createRuleTypes([
{
name: "region_id",
rule_attribute: "region_id",
},
{
name: "customer_group_id",
rule_attribute: "customer_group_id",
},
])
}

View File

@@ -13,18 +13,14 @@ const defaultPrices = [
},
]
const defaultPriceSetRules = [{ rule_attribute: "region_id" }]
export const createVariantPriceSet = async ({
container,
variantId,
prices = defaultPrices,
rules = defaultPriceSetRules,
}: {
container: MedusaContainer
variantId: string
prices?: CreatePriceSetDTO["prices"]
rules?: CreatePriceSetDTO["rules"]
}): Promise<PriceSetDTO> => {
const remoteLink = container.resolve("remoteLink")
const pricingModuleService: IPricingModuleService = container.resolve(
@@ -32,7 +28,6 @@ export const createVariantPriceSet = async ({
)
const priceSet = await pricingModuleService.createPriceSets({
rules,
prices,
})