feat(core-flows,medusa,pricing,types,utils): added price list workflows + endpoints (#6648)

Apologies for the giant PR in advance, but most of these are removing of files and migrations from old workflows and routes to new.

What:

- Adds CRUD endpoints for price lists
- Migrate workflows from old sdk to new
- Added missing updatePriceListPrices method to pricing module
This commit is contained in:
Riqwan Thamir
2024-03-15 12:09:02 +01:00
committed by GitHub
parent c3f26a6826
commit 9288f53327
81 changed files with 1959 additions and 2410 deletions

View File

@@ -0,0 +1,7 @@
---
"@medusajs/core-flows": patch
"@medusajs/medusa": patch
"@medusajs/utils": patch
---
feat(core-flows,medusa,utils): added price list prices upsert and delete workflows + endpoints

View File

@@ -0,0 +1,9 @@
---
"@medusajs/core-flows": patch
"@medusajs/pricing": patch
"@medusajs/medusa": patch
"@medusajs/types": patch
"@medusajs/utils": patch
---
feat(core-flows,medusa,pricing,types,utils): added price list workflows + endpoints

View File

@@ -1,175 +0,0 @@
import {
simpleCustomerGroupFactory,
simpleProductFactory,
simpleRegionFactory,
} from "../../../../factories"
import { IPricingModuleService } from "@medusajs/types"
import adminSeeder from "../../../../helpers/admin-seeder"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(50000)
const adminHeaders = {
headers: {
"x-medusa-access-token": "test_token",
},
}
const env = {
MEDUSA_FF_MEDUSA_V2: true,
}
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe.skip("POST /admin/price-lists", () => {
let appContainer
let product
let variant
let pricingModuleService: IPricingModuleService
beforeAll(async () => {
appContainer = getContainer()
pricingModuleService = appContainer.resolve("pricingModuleService")
})
beforeEach(async () => {
await adminSeeder(dbConnection)
await createDefaultRuleTypes(appContainer)
await simpleCustomerGroupFactory(dbConnection, {
id: "customer-group-1",
name: "Test Group",
})
await simpleRegionFactory(dbConnection, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
})
product = await simpleProductFactory(dbConnection, {
id: "test-product-with-variant",
variants: [
{
options: [{ option_id: "test-product-option-1", value: "test" }],
},
],
options: [
{
id: "test-product-option-1",
title: "Test option 1",
},
],
})
variant = product.variants[0]
})
it("should create price list and money amounts", async () => {
await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
const data = {
name: "test price list",
description: "test",
type: "override",
customer_groups: [{ id: "customer-group-1" }],
status: "active",
starts_at: new Date(),
prices: [
{
amount: 400,
variant_id: variant.id,
currency_code: "usd",
},
],
}
const result = await api.post(`admin/price-lists`, data, adminHeaders)
let response = await api.get(
`/admin/price-lists/${result.data.price_list.id}`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
name: "test price list",
description: "test",
type: "override",
status: "active",
starts_at: expect.any(String),
ends_at: null,
customer_groups: [
{
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
name: "Test Group",
metadata: null,
},
],
prices: [
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
currency_code: "usd",
amount: 400,
min_quantity: null,
max_quantity: null,
price_list_id: expect.any(String),
region_id: null,
variant: expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
title: expect.any(String),
product_id: expect.any(String),
sku: null,
barcode: null,
ean: null,
upc: null,
variant_rank: 0,
inventory_quantity: 10,
allow_backorder: false,
manage_inventory: true,
hs_code: null,
origin_country: null,
mid_code: null,
material: null,
weight: null,
length: null,
height: null,
width: null,
metadata: null,
}),
variant_id: expect.any(String),
}),
],
})
)
})
})
},
})

View File

@@ -1,128 +0,0 @@
import {
simpleProductFactory,
simpleRegionFactory,
} from "../../../../factories"
import { IPricingModuleService } from "@medusajs/types"
import adminSeeder from "../../../../helpers/admin-seeder"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(50000)
const adminHeaders = {
headers: {
"x-medusa-access-token": "test_token",
},
}
const env = {
MEDUSA_FF_MEDUSA_V2: true,
}
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe.skip("DELETE /admin/price-lists/:id", () => {
let appContainer
let product
let variant
let pricingModuleService: IPricingModuleService
beforeAll(async () => {
appContainer = getContainer()
pricingModuleService = appContainer.resolve("pricingModuleService")
})
beforeEach(async () => {
await adminSeeder(dbConnection)
await createDefaultRuleTypes(appContainer)
await simpleRegionFactory(dbConnection, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
})
product = await simpleProductFactory(dbConnection, {
id: "test-product-with-variant",
variants: [
{
options: [{ option_id: "test-product-option-1", value: "test" }],
},
],
options: [
{
id: "test-product-option-1",
title: "Test option 1",
},
],
})
variant = product.variants[0]
})
it("should delete price list and money amounts", async () => {
const priceSet = await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
const data = {
name: "test price list",
description: "test",
type: "override",
customer_groups: [],
status: "active",
prices: [
{
amount: 400,
variant_id: variant.id,
currency_code: "usd",
},
],
}
const result = await api.post(`admin/price-lists`, data, adminHeaders)
const priceListId = result.data.price_list.id
const getResponse = await api.get(
`/admin/price-lists/${priceListId}`,
adminHeaders
)
expect(getResponse.status).toEqual(200)
let psmas = await pricingModuleService.listPriceSetMoneyAmounts({
price_list_id: [priceListId],
})
expect(psmas.length).toEqual(1)
const deleteRes = await api.delete(
`/admin/price-lists/${priceListId}`,
adminHeaders
)
expect(deleteRes.status).toEqual(200)
const afterDelete = await api
.get(`/admin/price-lists/${priceListId}`, adminHeaders)
.catch((err) => {
return err
})
expect(afterDelete.response.status).toEqual(404)
psmas = await pricingModuleService.listPriceSetMoneyAmounts({
price_list_id: [priceListId],
})
expect(psmas.length).toEqual(0)
})
})
},
})

View File

@@ -1,184 +0,0 @@
import { simpleProductFactory } from "../../../../factories"
import {
IPricingModuleService,
PriceListStatus,
PriceListType,
} from "@medusajs/types"
import adminSeeder from "../../../../helpers/admin-seeder"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(50000)
const adminHeaders = {
headers: {
"x-medusa-access-token": "test_token",
},
}
const env = {
MEDUSA_FF_MEDUSA_V2: true,
}
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe.skip("GET /admin/price-lists/:id", () => {
let appContainer
let product
let variant
let pricingModuleService: IPricingModuleService
beforeAll(async () => {
appContainer = getContainer()
pricingModuleService = appContainer.resolve("pricingModuleService")
})
beforeEach(async () => {
await adminSeeder(dbConnection)
product = await simpleProductFactory(dbConnection, {
id: "test-product-with-variant",
variants: [
{
options: [{ option_id: "test-product-option-1", value: "test" }],
},
],
options: [
{
id: "test-product-option-1",
title: "Test option 1",
},
],
})
variant = product.variants[0]
})
it("should get price list and its money amounts with variants", async () => {
const priceSet = await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
rules: [],
})
const [priceList] = await pricingModuleService.createPriceLists([
{
title: "test price list",
description: "test",
ends_at: new Date(),
starts_at: new Date(),
status: PriceListStatus.ACTIVE,
type: PriceListType.OVERRIDE,
prices: [
{
amount: 5000,
currency_code: "usd",
price_set_id: priceSet.id,
},
],
},
])
await pricingModuleService.createPriceLists([
{
title: "test price list 1",
description: "test 1",
ends_at: new Date(),
starts_at: new Date(),
status: PriceListStatus.ACTIVE,
type: PriceListType.OVERRIDE,
prices: [
{
amount: 5000,
currency_code: "usd",
price_set_id: priceSet.id,
},
],
},
])
const response = await api.get(
`/admin/price-lists/${priceList.id}`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
name: "test price list",
description: "test",
type: "override",
status: "active",
starts_at: expect.any(String),
ends_at: expect.any(String),
customer_groups: [],
prices: [
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
currency_code: "usd",
amount: 5000,
min_quantity: null,
max_quantity: null,
price_list_id: expect.any(String),
region_id: null,
variant: expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
title: expect.any(String),
product_id: expect.any(String),
sku: null,
barcode: null,
ean: null,
upc: null,
variant_rank: 0,
inventory_quantity: 10,
allow_backorder: false,
manage_inventory: true,
hs_code: null,
origin_country: null,
mid_code: null,
material: null,
weight: null,
length: null,
height: null,
width: null,
metadata: null,
}),
variant_id: expect.any(String),
}),
],
})
)
})
it("should throw an error when price list is not found", async () => {
const error = await api
.get(`/admin/price-lists/does-not-exist`, adminHeaders)
.catch((e) => e)
expect(error.response.status).toBe(404)
expect(error.response.data).toEqual({
type: "not_found",
message: "Price list with id: does-not-exist was not found",
})
})
})
},
})

View File

@@ -1,152 +0,0 @@
import { simpleProductFactory } from "../../../../factories"
import {
IPricingModuleService,
PriceListStatus,
PriceListType,
} from "@medusajs/types"
import adminSeeder from "../../../../helpers/admin-seeder"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(50000)
const adminHeaders = {
headers: {
"x-medusa-access-token": "test_token",
},
}
const env = {
MEDUSA_FF_MEDUSA_V2: true,
}
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe.skip("GET /admin/price-lists", () => {
let appContainer
let product
let variant
let pricingModuleService: IPricingModuleService
beforeAll(async () => {
appContainer = getContainer()
pricingModuleService = appContainer.resolve("pricingModuleService")
})
beforeEach(async () => {
await adminSeeder(dbConnection)
product = await simpleProductFactory(dbConnection, {
id: "test-product-with-variant",
variants: [
{
options: [{ option_id: "test-product-option-1", value: "test" }],
},
],
options: [
{
id: "test-product-option-1",
title: "Test option 1",
},
],
})
variant = product.variants[0]
})
it("should get price list and its money amounts with variants", async () => {
const priceSet = await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
rules: [],
})
const [priceList] = await pricingModuleService.createPriceLists([
{
title: "test price list",
description: "test",
ends_at: new Date(),
starts_at: new Date(),
status: PriceListStatus.ACTIVE,
type: PriceListType.OVERRIDE,
prices: [
{
amount: 5000,
currency_code: "usd",
price_set_id: priceSet.id,
},
],
},
])
const response = await api.get(`/admin/price-lists`, adminHeaders)
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.price_lists).toEqual([
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
name: "test price list",
description: "test",
type: "override",
status: "active",
starts_at: expect.any(String),
ends_at: expect.any(String),
customer_groups: [],
prices: [
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
currency_code: "usd",
amount: 5000,
min_quantity: null,
max_quantity: null,
price_list_id: expect.any(String),
region_id: null,
variant: expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
title: expect.any(String),
product_id: expect.any(String),
sku: null,
barcode: null,
ean: null,
upc: null,
variant_rank: 0,
inventory_quantity: 10,
allow_backorder: false,
manage_inventory: true,
hs_code: null,
origin_country: null,
mid_code: null,
material: null,
weight: null,
length: null,
height: null,
width: null,
metadata: null,
}),
variant_id: expect.any(String),
}),
],
}),
])
})
})
},
})

View File

@@ -258,6 +258,351 @@ medusaIntegrationTestRunner({
})
})
})
describe("POST /admin/price-lists", () => {
it("should create price list and money amounts", async () => {
await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [{ amount: 3000, currency_code: "usd" }],
})
const data = {
title: "test price list",
description: "test",
type: "override",
status: "active",
starts_at: new Date(),
rules: {
customer_group_id: [customerGroup.id],
},
prices: [
{
amount: 400,
variant_id: variant.id,
currency_code: "usd",
rules: { region_id: region.id },
},
],
}
const response = await api.post(
`admin/price-lists`,
data,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
title: "test price list",
description: "test",
type: "override",
status: "active",
starts_at: expect.any(String),
ends_at: null,
rules: {
customer_group_id: [customerGroup.id],
},
prices: [
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
amount: 400,
min_quantity: null,
max_quantity: null,
variant_id: variant.id,
rules: {
region_id: region.id,
},
}),
],
})
)
})
it("should throw error when required attributes are not provided", async () => {
const data = {
prices: [
{
amount: 400,
currency_code: "usd",
},
],
}
const errorResponse = await api
.post(`admin/price-lists`, data, adminHeaders)
.catch((e) => e)
expect(errorResponse.response.status).toEqual(400)
expect(errorResponse.response.data.message).toEqual(
"title must be a string, description must be a string, type must be one of the following values: sale, override, variant_id must be a string"
)
})
})
describe("DELETE /admin/price-lists/:id", () => {
it("should delete price list and money amounts", async () => {
await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [{ amount: 3000, currency_code: "usd" }],
})
const data = {
title: "test price list",
description: "test",
type: "override",
status: "active",
starts_at: new Date(),
prices: [
{ amount: 400, variant_id: variant.id, currency_code: "usd" },
],
}
const result = await api.post(`admin/price-lists`, data, adminHeaders)
const priceListId = result.data.price_list.id
let psmas = await pricingModule.listPriceSetMoneyAmounts({
price_list_id: [priceListId],
})
expect(psmas.length).toEqual(1)
const deleteRes = await api.delete(
`/admin/price-lists/${priceListId}`,
adminHeaders
)
expect(deleteRes.status).toEqual(200)
const afterDelete = await api
.get(`/admin/price-lists/${priceListId}`, adminHeaders)
.catch((e) => e)
expect(afterDelete.response.status).toEqual(404)
psmas = await pricingModule.listPriceSetMoneyAmounts({
price_list_id: [priceListId],
})
expect(psmas.length).toEqual(0)
})
it("should idempotently return a success even if price lists dont exist", async () => {
const deleteRes = await api.delete(
`/admin/price-lists/does-not-exist`,
adminHeaders
)
expect(deleteRes.status).toEqual(200)
expect(deleteRes.data).toEqual({
id: "does-not-exist",
object: "price_list",
deleted: true,
})
})
})
describe("POST /admin/price-lists/:id", () => {
it("should throw error when trying to update a price list that does not exist", async () => {
const updateRes = await api
.post(
`admin/price-lists/does-not-exist`,
{ title: "new price list name" },
adminHeaders
)
.catch((e) => e)
expect(updateRes.response.status).toEqual(404)
expect(updateRes.response.data.message).toEqual(
"Price lists with id: does-not-exist was not found"
)
})
it("should update price lists successfully", async () => {
await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [{ amount: 3000, currency_code: "usd" }],
})
const [priceList] = await pricingModule.createPriceLists([
{
title: "test price list",
description: "test",
ends_at: new Date(),
starts_at: new Date(),
status: PriceListStatus.ACTIVE,
type: PriceListType.OVERRIDE,
},
])
const data = {
title: "new price list name",
description: "new price list description",
rules: {
customer_group_id: [customerGroup.id],
},
prices: [
{
amount: 400,
variant_id: variant.id,
currency_code: "usd",
rules: { region_id: region.id },
},
],
}
const response = await api.post(
`admin/price-lists/${priceList.id}`,
data,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
title: "new price list name",
description: "new price list description",
type: "override",
status: "active",
starts_at: expect.any(String),
ends_at: expect.any(String),
rules: {
customer_group_id: [customerGroup.id],
},
prices: [
{
id: expect.any(String),
currency_code: "usd",
amount: 400,
min_quantity: null,
max_quantity: null,
variant_id: variant.id,
rules: {
region_id: region.id,
},
},
],
})
)
})
})
describe("POST /admin/price-lists/:id/prices", () => {
it("should upsert price list prices successfully", async () => {
const priceSet = await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [{ amount: 3000, currency_code: "usd" }],
})
const [priceList] = await pricingModule.createPriceLists([
{
title: "test price list",
description: "test",
prices: [
{
id: "test-price-id",
amount: 5000,
currency_code: "usd",
price_set_id: priceSet.id,
rules: { region_id: region.id },
},
],
},
])
const data = {
prices: [
{
amount: 400,
variant_id: variant.id,
currency_code: "usd",
rules: { region_id: region.id },
},
{
id: "test-price-id",
variant_id: variant.id,
amount: 200,
},
],
}
const response = await api.post(
`admin/price-lists/${priceList.id}/prices`,
data,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.price_list.prices.length).toEqual(2)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
prices: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
amount: 400,
}),
expect.objectContaining({
id: "test-price-id",
currency_code: "usd",
amount: 200,
}),
]),
})
)
})
})
describe("DELETE /admin/price-lists/:id/prices", () => {
it("should delete price list prices", async () => {
const priceSet = await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [],
})
const [priceList] = await pricingModule.createPriceLists([
{
title: "test price list",
description: "test",
prices: [
{
id: "test-price-id",
amount: 5000,
currency_code: "usd",
price_set_id: priceSet.id,
rules: {
region_id: region.id,
},
},
],
},
])
let response = await api.delete(
`/admin/price-lists/${priceList.id}/prices`,
{ ...adminHeaders, data: { ids: ["test-price-id"] } }
)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
ids: ["test-price-id"],
object: "price_list_prices",
deleted: true,
})
})
})
})
},
})

View File

@@ -1,265 +0,0 @@
import {
simpleCustomerGroupFactory,
simpleProductFactory,
simpleRegionFactory,
} from "../../../../factories"
import {
IPricingModuleService,
PriceListStatus,
PriceListType,
} from "@medusajs/types"
import adminSeeder from "../../../../helpers/admin-seeder"
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(50000)
const adminHeaders = {
headers: {
"x-medusa-access-token": "test_token",
},
}
const env = {
MEDUSA_FF_MEDUSA_V2: true,
}
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe.skip("POST /admin/price-lists/:id", () => {
let appContainer
let product
let variant
let variant2
let pricingModuleService: IPricingModuleService
beforeAll(async () => {
appContainer = getContainer()
pricingModuleService = appContainer.resolve("pricingModuleService")
})
beforeEach(async () => {
await adminSeeder(dbConnection)
await createDefaultRuleTypes(appContainer)
await simpleCustomerGroupFactory(dbConnection, {
id: "customer-group-2",
name: "Test Group 2",
})
await simpleRegionFactory(dbConnection, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
})
product = await simpleProductFactory(dbConnection, {
id: "test-product-with-variant",
variants: [
{
options: [{ option_id: "test-product-option-1", value: "test" }],
},
{
options: [
{ option_id: "test-product-option-2", value: "test 2" },
],
},
],
options: [
{
id: "test-product-option-1",
title: "Test option 1",
},
{
id: "test-product-option-2",
title: "Test option 2",
},
],
})
variant = product.variants[0]
variant2 = product.variants[1]
})
it("should update price lists successfully with prices", async () => {
const var2PriceSet = await createVariantPriceSet({
container: appContainer,
variantId: variant2.id,
prices: [],
})
const [priceList] = await pricingModuleService.createPriceLists([
{
title: "test price list",
description: "test",
ends_at: new Date(),
starts_at: new Date(),
status: PriceListStatus.ACTIVE,
type: PriceListType.OVERRIDE,
prices: [
{
amount: 3000,
currency_code: "usd",
price_set_id: var2PriceSet.id,
},
],
},
])
await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
const data = {
name: "new price list name",
description: "new price list description",
customer_groups: [{ id: "customer-group-2" }],
prices: [
{
variant_id: variant.id,
amount: 5000,
currency_code: "usd",
},
{
id: priceList?.price_set_money_amounts?.[0].money_amount?.id,
amount: 6000,
currency_code: "usd",
variant_id: variant2.id,
},
],
}
await api.post(`admin/price-lists/${priceList.id}`, data, adminHeaders)
const response = await api.get(
`/admin/price-lists/${priceList.id}`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
name: "new price list name",
description: "new price list description",
type: "override",
status: "active",
starts_at: expect.any(String),
ends_at: expect.any(String),
customer_groups: [
{
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
name: "Test Group 2",
metadata: null,
},
],
prices: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
currency_code: "usd",
amount: 5000,
min_quantity: null,
max_quantity: null,
price_list_id: priceList.id,
region_id: null,
variant: expect.objectContaining({
id: variant.id,
}),
variant_id: variant.id,
}),
expect.objectContaining({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
currency_code: "usd",
amount: 6000,
min_quantity: null,
max_quantity: null,
price_list_id: priceList.id,
region_id: null,
variant: expect.objectContaining({
id: variant2.id,
}),
variant_id: variant2.id,
}),
]),
})
)
})
it("should not delete customer groups if customer_groups is not passed as a param", async () => {
await createVariantPriceSet({
container: appContainer,
variantId: variant2.id,
prices: [],
})
const [priceList] = await pricingModuleService.createPriceLists([
{
title: "test price list",
description: "test",
status: PriceListStatus.DRAFT,
rules: {
customer_group_id: ["customer-group-2"],
},
prices: [],
},
])
await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [],
})
const data = {
status: PriceListStatus.ACTIVE,
}
await api.post(`admin/price-lists/${priceList.id}`, data, adminHeaders)
const response = await api.get(
`/admin/price-lists/${priceList.id}`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
customer_groups: [
{
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
name: "Test Group 2",
metadata: null,
},
],
})
)
})
})
},
})

View File

@@ -2,5 +2,4 @@ export * from "./cart"
export * from "./inventory"
export * from "./line-item"
export * from "./payment-collection"
export * from "./price-list"
export * from "./product"

View File

@@ -1,74 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { PricingTypes, WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum CreatePriceListActions {
prepare = "prepare",
createPriceList = "createPriceList",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: CreatePriceListActions.prepare,
noCompensation: true,
next: {
action: CreatePriceListActions.createPriceList,
},
},
}
const handlers = new Map([
[
CreatePriceListActions.prepare,
{
invoke: pipe(
{
inputAlias: CreatePriceListActions.prepare,
merge: true,
invoke: {
from: CreatePriceListActions.prepare,
},
},
PriceListHandlers.prepareCreatePriceLists
),
},
],
[
CreatePriceListActions.createPriceList,
{
invoke: pipe(
{
invoke: {
from: CreatePriceListActions.prepare,
alias: PriceListHandlers.createPriceLists.aliases.priceLists,
},
},
PriceListHandlers.createPriceLists
),
compensate: pipe(
{
invoke: {
from: CreatePriceListActions.createPriceList,
alias: PriceListHandlers.removePriceLists.aliases.priceLists,
},
},
PriceListHandlers.removePriceLists
),
},
],
])
WorkflowManager.register(Workflows.CreatePriceList, workflowSteps, handlers)
export const createPriceLists = exportWorkflow<
WorkflowTypes.PriceListWorkflow.CreatePriceListWorkflowInputDTO,
{
priceList: PricingTypes.CreatePriceListDTO
}[]
>(Workflows.CreatePriceList, CreatePriceListActions.createPriceList)

View File

@@ -1,71 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum RemovePriceListPricesActions {
prepare = "prepare",
removePriceListPrices = "removePriceListPrices",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: RemovePriceListPricesActions.prepare,
noCompensation: true,
next: {
action: RemovePriceListPricesActions.removePriceListPrices,
noCompensation: true,
},
},
}
const handlers = new Map([
[
RemovePriceListPricesActions.prepare,
{
invoke: pipe(
{
inputAlias: RemovePriceListPricesActions.prepare,
merge: true,
invoke: {
from: RemovePriceListPricesActions.prepare,
},
},
PriceListHandlers.prepareRemovePriceListPrices
),
},
],
[
RemovePriceListPricesActions.removePriceListPrices,
{
invoke: pipe(
{
merge: true,
invoke: {
from: RemovePriceListPricesActions.prepare,
},
},
PriceListHandlers.removePrices
),
},
],
])
WorkflowManager.register(
Workflows.RemovePriceListPrices,
workflowSteps,
handlers
)
export const removePriceListPrices = exportWorkflow<
WorkflowTypes.PriceListWorkflow.RemovePriceListPricesWorkflowInputDTO,
string[]
>(
Workflows.RemovePriceListPrices,
RemovePriceListPricesActions.removePriceListPrices
)

View File

@@ -1,57 +0,0 @@
import { WorkflowTypes } from "@medusajs/types"
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { PriceListHandlers } from "../../handlers"
import { Workflows } from "../../definitions"
export enum RemovePriceListActions {
removePriceList = "removePriceList",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: RemovePriceListActions.removePriceList,
noCompensation: true,
},
}
const handlers = new Map([
[
RemovePriceListActions.removePriceList,
{
invoke: pipe(
{
inputAlias: RemovePriceListActions.removePriceList,
merge: true,
invoke: {
from: RemovePriceListActions.removePriceList,
},
},
PriceListHandlers.removePriceLists
),
},
],
])
WorkflowManager.register(Workflows.DeletePriceLists, workflowSteps, handlers)
export const removePriceLists = exportWorkflow<
WorkflowTypes.PriceListWorkflow.RemovePriceListWorkflowInputDTO,
{
price_list_ids: string[]
}
>(
Workflows.DeletePriceLists,
RemovePriceListActions.removePriceList,
async (data) => {
return {
price_lists: data.price_lists.map((priceListId) => ({
price_list: { id: priceListId },
})),
}
}
)

View File

@@ -1,72 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum RemoveProductPricesActions {
prepare = "prepare",
removePriceListPriceSetPrices = "removePriceListPriceSetPrices",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: RemoveProductPricesActions.prepare,
noCompensation: true,
next: {
action: RemoveProductPricesActions.removePriceListPriceSetPrices,
noCompensation: true,
},
},
}
const handlers = new Map([
[
RemoveProductPricesActions.prepare,
{
invoke: pipe(
{
inputAlias: RemoveProductPricesActions.prepare,
merge: true,
invoke: {
from: RemoveProductPricesActions.prepare,
},
},
PriceListHandlers.prepareRemoveProductPrices
),
},
],
[
RemoveProductPricesActions.removePriceListPriceSetPrices,
{
invoke: pipe(
{
merge: true,
invoke: {
from: RemoveProductPricesActions.prepare,
alias: PriceListHandlers.createPriceLists.aliases.priceLists,
},
},
PriceListHandlers.removePriceListPriceSetPrices
),
},
],
])
WorkflowManager.register(
Workflows.RemovePriceListProductPrices,
workflowSteps,
handlers
)
export const removePriceListProductPrices = exportWorkflow<
WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO,
string[]
>(
Workflows.RemovePriceListProductPrices,
RemoveProductPricesActions.removePriceListPriceSetPrices
)

View File

@@ -1,72 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum RemoveVariantPricesActions {
prepare = "prepare",
removePriceListPriceSetPrices = "removePriceListPriceSetPrices",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: RemoveVariantPricesActions.prepare,
noCompensation: true,
next: {
action: RemoveVariantPricesActions.removePriceListPriceSetPrices,
noCompensation: true,
},
},
}
const handlers = new Map([
[
RemoveVariantPricesActions.prepare,
{
invoke: pipe(
{
inputAlias: RemoveVariantPricesActions.prepare,
merge: true,
invoke: {
from: RemoveVariantPricesActions.prepare,
},
},
PriceListHandlers.prepareRemoveVariantPrices
),
},
],
[
RemoveVariantPricesActions.removePriceListPriceSetPrices,
{
invoke: pipe(
{
merge: true,
invoke: {
from: RemoveVariantPricesActions.prepare,
alias: PriceListHandlers.createPriceLists.aliases.priceLists,
},
},
PriceListHandlers.removePriceListPriceSetPrices
),
},
],
])
WorkflowManager.register(
Workflows.RemovePriceListVariantPrices,
workflowSteps,
handlers
)
export const removePriceListVariantPrices = exportWorkflow<
WorkflowTypes.PriceListWorkflow.RemovePriceListVariantsWorkflowInputDTO,
string[]
>(
Workflows.RemovePriceListVariantPrices,
RemoveVariantPricesActions.removePriceListPriceSetPrices
)

View File

@@ -1,65 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum UpdatePriceListActions {
prepare = "prepare",
updatePriceList = "updatePriceList",
}
const workflowSteps: TransactionStepsDefinition = {
action: UpdatePriceListActions.prepare,
noCompensation: true,
next: {
next: {
noCompensation: true,
action: UpdatePriceListActions.updatePriceList,
},
},
}
const handlers = new Map([
[
UpdatePriceListActions.prepare,
{
invoke: pipe(
{
inputAlias: UpdatePriceListActions.prepare,
merge: true,
invoke: {
from: UpdatePriceListActions.prepare,
},
},
PriceListHandlers.prepareUpdatePriceLists
),
},
],
[
UpdatePriceListActions.updatePriceList,
{
invoke: pipe(
{
inputAlias: UpdatePriceListActions.updatePriceList,
merge: true,
invoke: {
from: UpdatePriceListActions.prepare,
},
},
PriceListHandlers.updatePriceLists
),
},
],
])
WorkflowManager.register(Workflows.UpdatePriceLists, workflowSteps, handlers)
export const updatePriceLists = exportWorkflow<
WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowInputDTO,
{ priceList: WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowDTO }[]
>(Workflows.UpdatePriceLists, UpdatePriceListActions.updatePriceList)

View File

@@ -3,7 +3,6 @@ export * as CommonHandlers from "./common"
export * as CustomerHandlers from "./customer"
export * as InventoryHandlers from "./inventory"
export * as MiddlewaresHandlers from "./middlewares"
export * as PriceListHandlers from "./price-list"
export * as ProductHandlers from "./product"
export * as RegionHandlers from "./region"
export * as SalesChannelHandlers from "./sales-channel"

View File

@@ -1,42 +0,0 @@
import {
CreatePriceListDTO,
IPricingModuleService,
PriceListDTO,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
priceList: PriceListDTO
}[]
type Input = {
tag?: string
priceList: CreatePriceListDTO
}[]
export async function createPriceLists({
container,
data,
}: WorkflowArguments<{
priceLists: Input
}>): Promise<Result> {
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
return await Promise.all(
data.priceLists.map(async (item) => {
const [priceList] = await pricingService!.createPriceLists([
item.priceList,
])
return { tag: item.tag ?? priceList.id, priceList }
})
)
}
createPriceLists.aliases = {
priceLists: "priceLists",
}

View File

@@ -1,10 +0,0 @@
export * from "./create-price-list"
export * from "./prepare-create-price-list"
export * from "./prepare-update-price-lists"
export * from "./remove-price-list"
export * from "./update-price-lists"
export * from "./prepare-remove-product-prices"
export * from "./remove-price-set-price-list-prices"
export * from "./prepare-remove-variant-prices"
export * from "./prepare-remove-price-list-prices"
export * from "./remove-prices"

View File

@@ -1,108 +0,0 @@
import { CreatePriceListDTO, PriceListWorkflow } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
tag?: string
priceList: CreatePriceListDTO
}[]
export async function prepareCreatePriceLists({
container,
data,
}: WorkflowArguments<{
price_lists: (PriceListWorkflow.CreatePriceListWorkflowDTO & {
_associationTag?: string
})[]
}>): Promise<Result | void> {
const remoteQuery = container.resolve("remoteQuery")
const regionService = container.resolve("regionService")
const { price_lists } = data
const variantIds = price_lists
.map((priceList) => priceList.prices.map((price) => price.variant_id))
.flat()
const variables = {
variant_id: variantIds,
take: null,
}
const query = {
product_variant_price_set: {
__args: variables,
fields: ["variant_id", "price_set_id"],
},
}
const variantPriceSets = await remoteQuery(query)
const variantIdPriceSetIdMap: Map<string, string> = new Map(
variantPriceSets.map((variantPriceSet) => [
variantPriceSet.variant_id,
variantPriceSet.price_set_id,
])
)
const variantsWithoutPriceSets: string[] = []
for (const variantId of variantIds) {
if (!variantIdPriceSetIdMap.has(variantId)) {
variantsWithoutPriceSets.push(variantId)
}
}
if (variantsWithoutPriceSets.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`No priceSet exist for variants: ${variantsWithoutPriceSets.join(", ")}`
)
}
const regionIds = price_lists
.map(({ prices }) => prices?.map((price) => price.region_id) ?? [])
.flat(2)
const regions = await regionService.list({ id: regionIds }, {})
const regionIdCurrencyCodeMap: Map<string, string> = new Map(
regions.map((region: { id: string; currency_code: string }) => [
region.id,
region.currency_code,
])
)
return price_lists.map((priceListDTO) => {
priceListDTO.title ??= priceListDTO.name
const { _associationTag, name, prices, ...rest } = priceListDTO
const priceList = rest as CreatePriceListDTO
priceList.rules ??= {}
priceList.prices =
prices?.map((price) => {
const price_set_id = variantIdPriceSetIdMap.get(price.variant_id)!
const rules: Record<string, string> = {}
if (price.region_id) {
rules.region_id = price.region_id
}
return {
currency_code:
regionIdCurrencyCodeMap.get(price.region_id as string) ??
(price.currency_code as string),
amount: price.amount,
min_quantity: price.min_quantity,
max_quantity: price.max_quantity,
price_set_id,
rules,
}
}) ?? []
return { priceList, tag: _associationTag }
})
}
prepareCreatePriceLists.aliases = {
payload: "payload",
}

View File

@@ -1,49 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
moneyAmountIds: string[]
priceListId: string
}
export async function prepareRemovePriceListPrices({
container,
data,
}: WorkflowArguments<{
money_amount_ids: string[]
price_list_id: string
}>): Promise<Result | void> {
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
const {
price_list_id: priceListId,
money_amount_ids: moneyAmountIdsToDelete,
} = data
const moneyAmounts = await pricingService.listMoneyAmounts(
{ id: moneyAmountIdsToDelete },
{
relations: [
"price_set_money_amount",
"price_set_money_amount.price_list",
],
take: null,
}
)
const moneyAmountIds = moneyAmounts
.filter(
(moneyAmount) =>
moneyAmount?.price_set_money_amount?.price_list?.id === priceListId
)
.map((ma) => ma.id)
return { moneyAmountIds, priceListId }
}
prepareRemovePriceListPrices.aliases = {
payload: "payload",
}

View File

@@ -1,63 +0,0 @@
import { WorkflowArguments } from "@medusajs/workflows-sdk"
import { prepareCreatePriceLists } from "./prepare-create-price-list"
type Result = {
priceSetIds: string[]
priceListId: string
}
export async function prepareRemoveProductPrices({
container,
data,
}: WorkflowArguments<{
product_ids: string[]
price_list_id: string
}>): Promise<Result | void> {
const remoteQuery = container.resolve("remoteQuery")
const { price_list_id, product_ids } = data
const variables = {
id: product_ids,
take: null,
}
const query = {
product: {
__args: variables,
...defaultAdminProductRemoteQueryObject,
},
}
const productsWithVariantPriceSets: QueryResult[] = await remoteQuery(query)
const priceSetIds = productsWithVariantPriceSets
.map(({ variants }) => variants.map(({ price }) => price.price_set_id))
.flat()
return { priceSetIds, priceListId: price_list_id }
}
prepareCreatePriceLists.aliases = {
payload: "payload",
}
type QueryResult = {
id: string
variants: {
id: string
price: {
price_set_id: string
variant_id: string
}
}[]
}
const defaultAdminProductRemoteQueryObject = {
fields: ["id"],
variants: {
price: {
fields: ["variant_id", "price_set_id"],
},
},
}

View File

@@ -1,57 +0,0 @@
import { WorkflowArguments } from "@medusajs/workflows-sdk"
import { prepareCreatePriceLists } from "./prepare-create-price-list"
type Result = {
priceSetIds: string[]
priceListId: string
}
export async function prepareRemoveVariantPrices({
container,
data,
}: WorkflowArguments<{
variant_ids: string[]
price_list_id: string
}>): Promise<Result | void> {
const remoteQuery = container.resolve("remoteQuery")
const { price_list_id, variant_ids } = data
const variables = {
variant_id: variant_ids,
take: null,
}
const query = {
product_variant_price_set: {
__args: variables,
fields: ["variant_id", "price_set_id"],
},
}
const productsWithVariantPriceSets: QueryResult[] = await remoteQuery(query)
const priceSetIds = productsWithVariantPriceSets.map(
(variantPriceSet) => variantPriceSet.price_set_id
)
return { priceSetIds, priceListId: price_list_id }
}
prepareCreatePriceLists.aliases = {
payload: "payload",
}
type QueryResult = {
price_set_id: string
variant_id: string
}
const defaultAdminProductRemoteQueryObject = {
fields: ["id"],
variants: {
price: {
fields: ["variant_id", "price_set_id"],
},
},
}

View File

@@ -1,108 +0,0 @@
import {
PriceListPriceDTO,
UpdatePriceListDTO,
WorkflowTypes,
} from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
priceLists: UpdatePriceListDTO[]
priceListPricesMap: Map<string, PriceListPriceDTO[]>
}
export async function prepareUpdatePriceLists({
container,
data,
}: WorkflowArguments<{
price_lists: WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowDTO[]
}>): Promise<Result> {
const { price_lists: priceListsData } = data
const remoteQuery = container.resolve("remoteQuery")
const regionService = container.resolve("regionService")
const variantPriceSetMap = new Map<string, string>()
const priceListPricesMap = new Map<string, PriceListPriceDTO[]>()
const variantIds = priceListsData
.map((priceListData) => priceListData.prices?.map((p) => p.variant_id))
.flat()
const variables = {
variant_id: variantIds,
take: null,
}
const query = {
product_variant_price_set: {
__args: variables,
fields: ["variant_id", "price_set_id"],
},
}
const variantPriceSets = await remoteQuery(query)
for (const { variant_id, price_set_id } of variantPriceSets) {
variantPriceSetMap.set(variant_id, price_set_id)
}
const regionIds = priceListsData
.map(({ prices }) => prices?.map((price) => price.region_id) ?? [])
.flat(2)
const regions = await regionService.list({ id: regionIds })
const regionsMap: Map<string, string> = new Map(
regions.map((region: { id: string; currency_code: string }) => [
region.id,
region.currency_code,
])
)
const priceLists = priceListsData.map((priceListData) => {
const priceListPrices: PriceListPriceDTO[] = []
priceListData.prices?.forEach((price) => {
const { variant_id, ...priceData } = price
if (!variant_id) {
return
}
const rules: Record<string, string> = {}
if (price.region_id) {
rules.region_id = price.region_id
}
priceListPrices.push({
id: priceData.id,
price_set_id: variantPriceSetMap.get(variant_id) as string,
currency_code:
regionsMap.get(priceData.region_id as string) ??
(priceData.currency_code as string),
amount: priceData.amount,
min_quantity: priceData.min_quantity,
max_quantity: priceData.max_quantity,
rules,
})
return
})
priceListPricesMap.set(priceListData.id, priceListPrices)
delete priceListData?.prices
const priceListDataClone: UpdatePriceListDTO = {
...priceListData,
}
if (priceListData.name) {
priceListDataClone.title = priceListData.name
}
return priceListDataClone
})
return { priceLists, priceListPricesMap }
}
prepareUpdatePriceLists.aliases = {
payload: "prepare",
}

View File

@@ -1,31 +0,0 @@
import { IPricingModuleService, PriceListDTO } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
export async function removePriceLists({
container,
data,
}: WorkflowArguments<{
price_lists: {
price_list: PriceListDTO
}[]
}>): Promise<
{
price_list: PriceListDTO
}[]
> {
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
await pricingService!.deletePriceLists(
data.price_lists.map(({ price_list }) => price_list.id)
)
return data.price_lists
}
removePriceLists.aliases = {
priceLists: "priceLists",
}

View File

@@ -1,40 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
import { prepareCreatePriceLists } from "./prepare-create-price-list"
export async function removePriceListPriceSetPrices({
container,
data,
}: WorkflowArguments<{
priceSetIds: string[]
priceListId: string
}>): Promise<string[]> {
const { priceSetIds, priceListId } = data
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
const priceSetMoneyAmounts = await pricingService.listPriceSetMoneyAmounts(
{
price_set_id: priceSetIds,
price_list_id: [priceListId],
},
{
relations: ["money_amount"],
take: null,
}
)
const moneyAmountIDs = priceSetMoneyAmounts
.map((priceSetMoneyAmount) => priceSetMoneyAmount.money_amount?.id)
.filter((moneyAmountId): moneyAmountId is string => !!moneyAmountId)
await pricingService.deleteMoneyAmounts(moneyAmountIDs)
return moneyAmountIDs
}
prepareCreatePriceLists.aliases = {
payload: "payload",
}

View File

@@ -1,29 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
deletedPriceIds: string[]
}
export async function removePrices({
container,
data,
}: WorkflowArguments<{
moneyAmountIds: string[]
}>): Promise<Result> {
const { moneyAmountIds } = data
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
await pricingService.deleteMoneyAmounts(moneyAmountIds)
return {
deletedPriceIds: moneyAmountIds,
}
}
removePrices.aliases = {
payload: "payload",
}

View File

@@ -1,63 +0,0 @@
import {
AddPriceListPricesDTO,
IPricingModuleService,
PriceListDTO,
PriceListPriceDTO,
UpdateMoneyAmountDTO,
UpdatePriceListDTO,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
priceLists: PriceListDTO[]
}
export async function updatePriceLists({
container,
data,
}: WorkflowArguments<{
priceLists: UpdatePriceListDTO[]
priceListPricesMap: Map<string, PriceListPriceDTO[]>
}>): Promise<Result> {
const { priceLists: priceListsData, priceListPricesMap } = data
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
const priceLists = await pricingService.updatePriceLists(priceListsData)
const addPriceListPricesData: AddPriceListPricesDTO[] = []
const moneyAmountsToUpdate: UpdateMoneyAmountDTO[] = []
for (const [priceListId, prices] of priceListPricesMap.entries()) {
const moneyAmountsToCreate: PriceListPriceDTO[] = []
for (const price of prices) {
if (price.id) {
moneyAmountsToUpdate.push(price as UpdateMoneyAmountDTO)
} else {
moneyAmountsToCreate.push(price)
}
}
addPriceListPricesData.push({
priceListId,
prices: moneyAmountsToCreate,
})
}
if (addPriceListPricesData.length) {
await pricingService.addPriceListPrices(addPriceListPricesData)
}
if (moneyAmountsToUpdate.length) {
await pricingService.updateMoneyAmounts(moneyAmountsToUpdate)
}
return { priceLists }
}
updatePriceLists.aliases = {
payload: "updatePriceLists",
}

View File

@@ -8,6 +8,7 @@ export * from "./fulfillment"
export * as Handlers from "./handlers"
export * from "./invite"
export * from "./payment"
export * from "./price-list"
export * from "./pricing"
export * from "./product"
export * from "./promotion"

View File

@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"

View File

@@ -0,0 +1,58 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CreatePriceListDTO,
CreatePriceListWorkflowInputDTO,
IPricingModuleService,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type WorkflowStepInput = {
data: CreatePriceListWorkflowInputDTO[]
variant_price_map: Record<string, string>
}
export const createPriceListsStepId = "create-price-lists"
export const createPriceListsStep = createStep(
createPriceListsStepId,
async (stepInput: WorkflowStepInput, { container }) => {
const { data, variant_price_map: variantPriceMap } = stepInput
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const createData = data.map((priceListDTO) => {
const { prices = [], ...rest } = priceListDTO
const createPriceListData: CreatePriceListDTO = { ...rest }
createPriceListData.prices = prices.map((price) => ({
currency_code: price.currency_code,
amount: price.amount,
min_quantity: price.min_quantity,
max_quantity: price.max_quantity,
price_set_id: variantPriceMap[price.variant_id],
rules: price.rules,
}))
return createPriceListData
})
const createdPriceLists = await pricingModule.createPriceLists(createData)
return new StepResponse(
createdPriceLists,
createdPriceLists.map((createdPriceLists) => createdPriceLists.id)
)
},
async (createdPriceListIds, { container }) => {
if (!createdPriceListIds?.length) {
return
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
await pricingModule.deletePriceLists(createdPriceListIds)
}
)

View File

@@ -0,0 +1,30 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const deletePriceListsStepId = "delete-campaigns"
export const deletePriceListsStep = createStep(
deletePriceListsStepId,
async (ids: string[], { container }) => {
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
// TODO: Implement soft delete price lists
await pricingModule.deletePriceLists(ids)
return new StepResponse(void 0, ids)
},
async (idsToRestore, { container }) => {
if (!idsToRestore?.length) {
return
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
// TODO: Implement restore price lists
// await pricingModule.restorePriceLists(idsToRestore)
}
)

View File

@@ -0,0 +1,7 @@
export * from "./create-price-lists"
export * from "./delete-price-lists"
export * from "./remove-price-list-prices"
export * from "./update-price-lists"
export * from "./upsert-price-list-prices"
export * from "./validate-price-lists"
export * from "./validate-variant-price-links"

View File

@@ -0,0 +1,41 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const removePriceListPricesStepId = "remove-price-list-prices"
export const removePriceListPricesStep = createStep(
removePriceListPricesStepId,
async (ids: string[], { container }) => {
if (!ids.length) {
return new StepResponse(null, [])
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const psmas = await pricingModule.listPriceSetMoneyAmounts(
{ id: ids },
{ relations: ["price_list"] }
)
await pricingModule.removePrices(psmas.map((psma) => psma.id))
return new StepResponse(
null,
psmas.map((psma) => psma.id)
)
},
async (ids, { container }) => {
if (!ids) {
return
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
// TODO: This needs to be implemented
// pricingModule.restorePrices(ids)
}
)

View File

@@ -0,0 +1,105 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
IPricingModuleService,
UpdatePriceListDTO,
UpdatePriceListWorkflowInputDTO,
} from "@medusajs/types"
import {
buildPriceListRules,
convertItemResponseToUpdateRequest,
getSelectsAndRelationsFromObjectArray,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const updatePriceListsStepId = "update-price-lists"
export const updatePriceListsStep = createStep(
updatePriceListsStepId,
async (data: UpdatePriceListDTO[], { container }) => {
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const { dataBeforeUpdate, selects, relations } = await getDataBeforeUpdate(
pricingModule,
data
)
const updatedPriceLists = await pricingModule.updatePriceLists(data)
return new StepResponse(updatedPriceLists, {
dataBeforeUpdate,
selects,
relations,
})
},
async (revertInput, { container }) => {
if (!revertInput) {
return
}
const { dataBeforeUpdate, selects, relations } = revertInput
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
await pricingModule.updatePriceLists(
dataBeforeUpdate.map((data) => {
const { price_list_rules: priceListRules = [], rules, ...rest } = data
const updateData: UpdatePriceListDTO = {
...rest,
rules: buildPriceListRules(priceListRules),
}
return convertItemResponseToUpdateRequest(
updateData,
selects,
relations
)
})
)
}
)
// Since rules is an API level abstraction, we need to do this dance of data fetching
// to its actual attributes in the module to do perform a revert in case a rollback needs to happen.
// TODO: Check if there is a better way to approach this. Preferably the module should be handling this
// if this is not the response the module provides.
async function getDataBeforeUpdate(
pricingModule: IPricingModuleService,
data: UpdatePriceListWorkflowInputDTO[]
) {
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data, {
objectFields: ["rules"],
})
const selectsClone = [...selects]
const relationsClone = [...relations]
if (selectsClone.includes("rules")) {
const index = selectsClone.indexOf("rules", 0)
if (index > -1) {
selectsClone.splice(index, 1)
}
selectsClone.push(
"price_list_rules.price_list_rule_values.value",
"price_list_rules.rule_type.rule_attribute"
)
relationsClone.push(
"price_list_rules.price_list_rule_values",
"price_list_rules.rule_type"
)
}
const dataBeforeUpdate = await pricingModule.listPriceLists(
{ id: data.map((d) => d.id) },
{ relations: relationsClone, select: selectsClone }
)
return {
dataBeforeUpdate,
selects,
relations,
}
}

View File

@@ -0,0 +1,131 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
AddPriceListPricesDTO,
CreatePriceListPriceDTO,
CreatePriceListPriceWorkflowDTO,
IPricingModuleService,
PriceSetMoneyAmountDTO,
UpdatePriceListPriceDTO,
UpdatePriceListPriceWorkflowDTO,
UpdatePriceListPricesDTO,
UpdatePriceListWorkflowInputDTO,
} from "@medusajs/types"
import { buildPriceSetPricesForModule, promiseAll } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type WorkflowStepInput = {
data: Pick<UpdatePriceListWorkflowInputDTO, "id" | "prices">[]
variant_price_map: Record<string, string>
}
export const upsertPriceListPricesStepId = "upsert-price-list-prices"
export const upsertPriceListPricesStep = createStep(
upsertPriceListPricesStepId,
async (stepInput: WorkflowStepInput, { container }) => {
const { data, variant_price_map: variantPriceSetMap } = stepInput
const priceListPricesToUpdate: UpdatePriceListPricesDTO[] = []
const priceListPricesToAdd: AddPriceListPricesDTO[] = []
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
for (const upsertPriceListPricesData of data) {
const { prices = [], id } = upsertPriceListPricesData
const pricesToAdd: CreatePriceListPriceDTO[] = []
const pricesToUpdate: UpdatePriceListPriceDTO[] = []
for (const price of prices) {
const priceSetId = variantPriceSetMap[price.variant_id!]
if (isPriceUpdate(price)) {
pricesToUpdate.push({ ...price, price_set_id: priceSetId })
} else {
pricesToAdd.push({ ...price, price_set_id: priceSetId })
}
}
if (pricesToUpdate.length) {
priceListPricesToUpdate.push({
price_list_id: id,
prices: pricesToUpdate,
})
}
if (pricesToAdd.length) {
priceListPricesToAdd.push({
price_list_id: id,
prices: pricesToAdd,
})
}
}
const updatedPriceSetMoneyAmounts =
await pricingModule.listPriceSetMoneyAmounts(
{
id: priceListPricesToUpdate
.map((priceListData) =>
priceListData.prices.map((price) => price.id)
)
.filter(Boolean)
.flat(1),
},
{ relations: ["price_list"] }
)
const priceListPsmaMap = new Map<string, PriceSetMoneyAmountDTO[]>()
const dataBeforePriceUpdate: UpdatePriceListPricesDTO[] = []
for (const priceSetMoneyAmount of updatedPriceSetMoneyAmounts) {
const priceListId = priceSetMoneyAmount.price_list!.id
const psmas = priceListPsmaMap.get(priceListId) || []
priceListPsmaMap.set(priceListId, psmas)
}
for (const [priceListId, psmas] of Object.entries(priceListPsmaMap)) {
dataBeforePriceUpdate.push({
price_list_id: priceListId,
prices: buildPriceSetPricesForModule(psmas),
})
}
// TODO: `addPriceListPrices` will return a list of price lists
// This should be reworked to return prices instead, as we need to
// do a revert incase this step fails
const [createdPriceListPrices, _] = await promiseAll([
pricingModule.addPriceListPrices(priceListPricesToAdd),
pricingModule.updatePriceListPrices(priceListPricesToUpdate),
])
return new StepResponse(null, {
createdPriceListPrices,
updatedPriceListPrices: dataBeforePriceUpdate,
})
},
async (data, { container }) => {
if (!data) {
return
}
const { createdPriceListPrices = [], updatedPriceListPrices = [] } = data
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
if (createdPriceListPrices.length) {
await pricingModule.removePrices(createdPriceListPrices.map((p) => p.id))
}
if (updatedPriceListPrices.length) {
await pricingModule.updatePriceListPrices(updatedPriceListPrices)
}
}
)
function isPriceUpdate(
data: UpdatePriceListPriceWorkflowDTO | CreatePriceListPriceWorkflowDTO
): data is UpdatePriceListPriceWorkflowDTO {
return "id" in data
}

View File

@@ -0,0 +1,41 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
IPricingModuleService,
PriceListDTO,
UpdatePriceListDTO,
} from "@medusajs/types"
import { MedusaError, arrayDifference } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const validatePriceListsStepId = "validate-price-lists"
export const validatePriceListsStep = createStep(
validatePriceListsStepId,
async (data: Pick<UpdatePriceListDTO, "id">[], { container }) => {
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const priceListIds = data.map((d) => d.id)
const priceLists = await pricingModule.listPriceLists({ id: priceListIds })
const diff = arrayDifference(
priceListIds,
priceLists.map((pl) => pl.id)
)
if (diff.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Price lists with id: ${diff.join(", ")} was not found`
)
}
const priceListMap: Record<string, PriceListDTO> = {}
for (const priceList of priceLists) {
priceListMap[priceList.id] = priceList
}
return new StepResponse(priceListMap)
}
)

View File

@@ -0,0 +1,48 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type WorkflowStepInput = Pick<UpdatePriceListWorkflowInputDTO, "prices">[]
export const validateVariantPriceLinksStepId = "validate-variant-price-links"
export const validateVariantPriceLinksStep = createStep(
validateVariantPriceLinksStepId,
async (data: WorkflowStepInput, { container }) => {
const remoteQuery = container.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const variantIds: string[] = data
.map((pl) => pl?.prices?.map((price) => price.variant_id!) || [])
.filter(Boolean)
.flat(1)
const variantPricingLinkQuery = remoteQueryObjectFromString({
entryPoint: "product_variant_price_set",
fields: ["variant_id", "price_set_id"],
variables: { variant_id: variantIds, take: null },
})
const links = await remoteQuery(variantPricingLinkQuery)
const variantPriceSetMap: Record<string, string> = {}
for (const link of links) {
variantPriceSetMap[link.variant_id] = link.price_set_id
}
const withoutLinks = variantIds.filter((id) => !variantPriceSetMap[id])
if (withoutLinks.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`No price set exist for variants: ${withoutLinks.join(", ")}`
)
}
return new StepResponse(variantPriceSetMap)
}
)

View File

@@ -0,0 +1,20 @@
import { CreatePriceListWorkflowInputDTO, PriceListDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createPriceListsStep, validateVariantPriceLinksStep } from "../steps"
type WorkflowInput = { price_lists_data: CreatePriceListWorkflowInputDTO[] }
export const createPriceListsWorkflowId = "create-price-lists"
export const createPriceListsWorkflow = createWorkflow(
createPriceListsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<PriceListDTO[]> => {
const variantPriceMap = validateVariantPriceLinksStep(
input.price_lists_data
)
return createPriceListsStep({
data: input.price_lists_data,
variant_price_map: variantPriceMap,
})
}
)

View File

@@ -0,0 +1,12 @@
import { createWorkflow, WorkflowData } from "@medusajs/workflows-sdk"
import { deletePriceListsStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const deletePriceListsWorkflowId = "delete-price-lists"
export const deletePriceListsWorkflow = createWorkflow(
deletePriceListsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
return deletePriceListsStep(input.ids)
}
)

View File

@@ -1,6 +1,5 @@
export * from "./create-price-lists"
export * from "./remove-price-lists"
export * from "./delete-price-lists"
export * from "./remove-price-list-prices"
export * from "./remove-product-prices"
export * from "./remove-variant-prices"
export * from "./update-price-lists"
export * from "./upsert-price-list-prices"

View File

@@ -0,0 +1,12 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { removePriceListPricesStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const removePriceListPricesWorkflowId = "remove-price-list-prices"
export const removePriceListPricesWorkflow = createWorkflow(
removePriceListPricesWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
removePriceListPricesStep(input.ids)
}
)

View File

@@ -0,0 +1,47 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import {
updatePriceListsStep,
upsertPriceListPricesStep,
validatePriceListsStep,
validateVariantPriceLinksStep,
} from "../steps"
type WorkflowInput = { price_lists_data: UpdatePriceListWorkflowInputDTO[] }
export const updatePriceListsWorkflowId = "update-price-lists"
export const updatePriceListsWorkflow = createWorkflow(
updatePriceListsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
const [priceListsMap, variantPriceMap] = parallelize(
validatePriceListsStep(input.price_lists_data),
validateVariantPriceLinksStep(input.price_lists_data)
)
const updatePricesInput = transform(
{ priceListsMap, variantPriceMap, input },
(data) => ({
data: data.input.price_lists_data,
price_lists_map: data.priceListsMap,
variant_price_map: data.variantPriceMap,
})
)
upsertPriceListPricesStep(updatePricesInput)
const updatePriceListInput = transform({ input }, (data) => {
return data.input.price_lists_data.map((priceListData) => {
delete priceListData.prices
return priceListData
})
})
updatePriceListsStep(updatePriceListInput)
}
)

View File

@@ -0,0 +1,31 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
} from "@medusajs/workflows-sdk"
import {
upsertPriceListPricesStep,
validatePriceListsStep,
validateVariantPriceLinksStep,
} from "../steps"
type WorkflowInput = {
price_lists_data: Pick<UpdatePriceListWorkflowInputDTO, "id" | "prices">[]
}
export const upsertPriceListPricesWorkflowId = "upsert-price-list-prices"
export const upsertPriceListPricesWorkflow = createWorkflow(
upsertPriceListPricesWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
const [_, variantPriceMap] = parallelize(
validatePriceListsStep(input.price_lists_data),
validateVariantPriceLinksStep(input.price_lists_data)
)
upsertPriceListPricesStep({
data: input.price_lists_data,
variant_price_map: variantPriceMap,
})
}
)

View File

@@ -1,4 +1,4 @@
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
import { Type } from "class-transformer"
import {
IsArray,
IsDateString,
@@ -9,10 +9,9 @@ import {
IsString,
ValidateNested,
} from "class-validator"
import { Transform, Type } from "class-transformer"
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
import { CampaignBudgetType } from "@medusajs/utils"
import { transformOptionalDate } from "../../../utils/validators/date-transform"
export class AdminGetCampaignsCampaignParams extends FindParams {}

View File

@@ -0,0 +1,68 @@
import {
removePriceListPricesWorkflow,
upsertPriceListPricesWorkflow,
} from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import { listPriceLists } from "../../queries"
import {
adminPriceListRemoteQueryFields,
defaultAdminPriceListFields,
} from "../../query-config"
import {
AdminDeletePriceListsPriceListPricesReq,
AdminPostPriceListsPriceListPricesReq,
} from "../../validators"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPriceListsPriceListPricesReq>,
res: MedusaResponse
) => {
const { prices } = req.validatedBody
const id = req.params.id
const workflow = upsertPriceListPricesWorkflow(req.scope)
const { errors } = await workflow.run({
input: {
price_lists_data: [{ id, prices }],
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const [[priceList]] = await listPriceLists({
container: req.scope,
remoteQueryFields: adminPriceListRemoteQueryFields,
apiFields: defaultAdminPriceListFields,
variables: { filters: { id }, skip: 0, take: 1 },
})
res.status(200).json({ price_list: priceList })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest<AdminDeletePriceListsPriceListPricesReq>,
res: MedusaResponse
) => {
const { ids } = req.validatedBody
const workflow = removePriceListPricesWorkflow(req.scope)
const { errors } = await workflow.run({
input: { ids },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
res.status(200).json({
ids,
object: "price_list_prices",
deleted: true,
})
}

View File

@@ -1,10 +1,18 @@
import {
deletePriceListsWorkflow,
updatePriceListsWorkflow,
} from "@medusajs/core-flows"
import { MedusaError } from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { listPriceLists } from "../queries"
import { adminPriceListRemoteQueryFields } from "../query-config"
import {
adminPriceListRemoteQueryFields,
defaultAdminPriceListFields,
} from "../query-config"
import { AdminPostPriceListsPriceListReq } from "../validators"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -31,3 +39,52 @@ export const GET = async (
res.status(200).json({ price_list: priceList })
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPriceListsPriceListReq>,
res: MedusaResponse
) => {
const id = req.params.id
const workflow = updatePriceListsWorkflow(req.scope)
const { errors } = await workflow.run({
input: { price_lists_data: [{ id, ...req.validatedBody }] },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const [[priceList]] = await listPriceLists({
container: req.scope,
remoteQueryFields: adminPriceListRemoteQueryFields,
apiFields: defaultAdminPriceListFields,
variables: { filters: { id }, skip: 0, take: 1 },
})
res.status(200).json({ price_list: priceList })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const id = req.params.id
const workflow = deletePriceListsWorkflow(req.scope)
const { errors } = await workflow.run({
input: { ids: [id] },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
res.status(200).json({
id,
object: "price_list",
deleted: true,
})
}

View File

@@ -1,10 +1,14 @@
import { transformQuery } from "../../../api/middlewares"
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { authenticate } from "../../../utils/authenticate-middleware"
import * as QueryConfig from "./query-config"
import {
AdminDeletePriceListsPriceListPricesReq,
AdminGetPriceListsParams,
AdminGetPriceListsPriceListParams,
AdminPostPriceListsPriceListPricesReq,
AdminPostPriceListsPriceListReq,
AdminPostPriceListsReq,
} from "./validators"
export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
@@ -33,4 +37,24 @@ export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
),
],
},
{
method: ["POST"],
matcher: "/admin/price-lists",
middlewares: [transformBody(AdminPostPriceListsReq)],
},
{
method: ["POST"],
matcher: "/admin/price-lists/:id",
middlewares: [transformBody(AdminPostPriceListsPriceListReq)],
},
{
method: ["POST"],
matcher: "/admin/price-lists/:id/prices",
middlewares: [transformBody(AdminPostPriceListsPriceListPricesReq)],
},
{
method: ["DELETE"],
matcher: "/admin/price-lists/:id/prices",
middlewares: [transformBody(AdminDeletePriceListsPriceListPricesReq)],
},
]

View File

@@ -1,11 +1,8 @@
import {
MedusaContainer,
PriceListRuleDTO,
PriceSetMoneyAmountDTO,
ProductVariantDTO,
} from "@medusajs/types"
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
buildPriceListRules,
buildPriceSetPricesForCore,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { cleanResponseData } from "../../../../utils/clean-response-data"
@@ -37,7 +34,7 @@ export async function listPriceLists({
for (const priceList of priceLists) {
priceList.rules = buildPriceListRules(priceList.price_list_rules || [])
priceList.prices = buildPriceSetPrices(
priceList.prices = buildPriceSetPricesForCore(
priceList.price_set_money_amounts || []
)
}
@@ -48,43 +45,3 @@ export async function listPriceLists({
return [sanitizedPriceLists, metadata.count]
}
function buildPriceListRules(
priceListRules: PriceListRuleDTO[]
): Record<string, string[]> {
return priceListRules.reduce((acc, curr) => {
const ruleAttribute = curr.rule_type.rule_attribute
const ruleValues = curr.price_list_rule_values || []
if (ruleAttribute) {
acc[ruleAttribute] = ruleValues.map((ruleValue) => ruleValue.value)
}
return acc
}, {})
}
function buildPriceSetPrices(
priceSetMoneyAmounts: (PriceSetMoneyAmountDTO & {
price_set: PriceSetMoneyAmountDTO["price_set"] & {
variant?: ProductVariantDTO
}
})[]
): Record<string, any>[] {
return priceSetMoneyAmounts.map((priceSetMoneyAmount) => {
const productVariant = priceSetMoneyAmount.price_set?.variant
const rules = priceSetMoneyAmount.price_rules?.reduce((acc, curr) => {
if (curr.rule_type.rule_attribute) {
acc[curr.rule_type.rule_attribute] = curr.value
}
return acc
}, {})
return {
...priceSetMoneyAmount.money_amount,
variant_id: productVariant?.id ?? null,
rules,
}
})
}

View File

@@ -1,9 +1,14 @@
import { createPriceListsWorkflow } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { listPriceLists } from "./queries"
import { adminPriceListRemoteQueryFields } from "./query-config"
import {
adminPriceListRemoteQueryFields,
defaultAdminPriceListFields,
} from "./query-config"
import { AdminPostPriceListsReq } from "./validators"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -29,3 +34,31 @@ export const GET = async (
limit,
})
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPriceListsReq>,
res: MedusaResponse
) => {
const workflow = createPriceListsWorkflow(req.scope)
const { result, errors } = await workflow.run({
input: { price_lists_data: [req.validatedBody] },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const [[priceList]] = await listPriceLists({
container: req.scope,
apiFields: defaultAdminPriceListFields,
remoteQueryFields: adminPriceListRemoteQueryFields,
variables: {
filters: { id: result[0].id },
skip: 0,
take: 1,
},
})
res.status(200).json({ price_list: priceList })
}

View File

@@ -1,4 +1,152 @@
import { PriceListStatus, PriceListType } from "@medusajs/types"
import { Transform, Type } from "class-transformer"
import {
IsArray,
IsEnum,
IsInt,
IsObject,
IsOptional,
IsString,
ValidateIf,
ValidateNested,
} from "class-validator"
import { FindParams } from "../../../types/common"
import { transformOptionalDate } from "../../../utils/validators/date-transform"
export class AdminGetPriceListsParams extends FindParams {}
export class AdminGetPriceListsPriceListParams extends FindParams {}
export class AdminPostPriceListsReq {
@IsString()
title: string
@IsString()
description: string
@IsOptional()
@Transform(transformOptionalDate)
starts_at?: string
@IsOptional()
@Transform(transformOptionalDate)
ends_at?: string
@IsOptional()
@IsEnum(PriceListStatus)
status?: PriceListStatus
@IsEnum(PriceListType)
type: PriceListType
@IsArray()
@Type(() => AdminPriceListPricesCreateReq)
@ValidateNested({ each: true })
prices: AdminPriceListPricesCreateReq[]
@IsOptional()
@IsObject()
rules?: Record<string, string[]>
}
export class AdminPriceListPricesCreateReq {
@IsString()
currency_code: string
@IsInt()
amount: number
@IsString()
variant_id: string
@IsOptional()
@IsInt()
min_quantity?: number
@IsOptional()
@IsInt()
max_quantity?: number
@IsOptional()
@IsObject()
rules?: Record<string, string>
}
export class AdminPostPriceListsPriceListReq {
@IsString()
@IsOptional()
title?: string
@IsString()
@IsOptional()
description?: string
@IsOptional()
@Transform(transformOptionalDate)
starts_at?: string
@IsOptional()
@Transform(transformOptionalDate)
ends_at?: string
@IsOptional()
@IsEnum(PriceListStatus)
status?: PriceListStatus
@IsOptional()
@IsEnum(PriceListType)
type?: PriceListType
@IsOptional()
@IsArray()
prices: (AdminPriceListPricesCreateReq | AdminPriceListPricesUpdateReq)[]
@IsOptional()
@IsObject()
rules?: Record<string, string[]>
}
export class AdminPostPriceListsPriceListPricesReq {
@IsOptional()
@IsArray()
prices: (AdminPriceListPricesCreateReq | AdminPriceListPricesUpdateReq)[]
}
export class AdminDeletePriceListsPriceListPricesReq {
@IsOptional()
@IsArray()
@IsString({ each: true })
ids: string[]
}
export class AdminPriceListPricesUpdateReq {
@IsOptional()
@IsString()
id: string
@IsOptional()
@ValidateIf((object) => !object.id)
@IsString()
currency_code?: string
@IsOptional()
@ValidateIf((object) => !object.id)
@IsInt()
amount?: number
@IsOptional()
@ValidateIf((object) => !object.id)
@IsString()
variant_id: string
@IsOptional()
@IsInt()
min_quantity?: number
@IsOptional()
@IsInt()
max_quantity?: number
@IsOptional()
@IsObject()
rules?: Record<string, string>
}

View File

@@ -1,6 +1,3 @@
import { updatePriceLists } from "@medusajs/core-flows"
import { MedusaContainer } from "@medusajs/types"
import { MedusaV2Flag } from "@medusajs/utils"
import { Type } from "class-transformer"
import { IsArray, IsBoolean, IsOptional, ValidateNested } from "class-validator"
import { EntityManager } from "typeorm"
@@ -9,7 +6,6 @@ import { PriceList } from "../../../.."
import PriceListService from "../../../../services/price-list"
import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list"
import { validator } from "../../../../utils/validator"
import { getPriceListPricingModule } from "./modules-queries"
/**
* @oas [post] /admin/price-lists/{id}/prices/batch
@@ -124,48 +120,22 @@ import { getPriceListPricingModule } from "./modules-queries"
*/
export default async (req, res) => {
const { id } = req.params
let priceList
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
const manager: EntityManager = req.scope.resolve("manager")
const priceListService: PriceListService =
req.scope.resolve("priceListService")
const validated = await validator(AdminPostPriceListPricesPricesReq, req.body)
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
const updatePriceListWorkflow = updatePriceLists(req.scope)
await manager.transaction(async (transactionManager) => {
await priceListService
.withTransaction(transactionManager)
.addPrices(id, validated.prices, validated.override)
})
const input = {
price_lists: [
{
id,
...validated,
},
],
}
await updatePriceListWorkflow.run({
input,
context: {
manager,
},
})
priceList = await getPriceListPricingModule(id, {
container: req.scope as MedusaContainer,
})
} else {
await manager.transaction(async (transactionManager) => {
await priceListService
.withTransaction(transactionManager)
.addPrices(id, validated.prices, validated.override)
})
priceList = await priceListService.retrieve(id, {
select: defaultAdminPriceListFields as (keyof PriceList)[],
relations: defaultAdminPriceListRelations,
})
}
const priceList = await priceListService.retrieve(id, {
select: defaultAdminPriceListFields as (keyof PriceList)[],
relations: defaultAdminPriceListRelations,
})
res.json({ price_list: priceList })
}

View File

@@ -1,11 +1,4 @@
import { MedusaContainer, PricingTypes, WorkflowTypes } from "@medusajs/types"
import {
FlagRouter,
MedusaV2Flag,
PriceListStatus,
PriceListType,
} from "@medusajs/utils"
import { createPriceLists } from "@medusajs/core-flows"
import { PriceListStatus, PriceListType } from "@medusajs/utils"
import { Transform, Type } from "class-transformer"
import {
IsArray,
@@ -26,7 +19,6 @@ import {
CreatePriceListInput,
} from "../../../../types/price-list"
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
import { getPriceListPricingModule } from "./modules-queries"
import { transformOptionalDate } from "../../../../utils/validators/date-transform"
/**
@@ -155,57 +147,17 @@ export default async (req: Request, res) => {
req.scope.resolve("priceListService")
const manager: EntityManager = req.scope.resolve("manager")
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
let priceList
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
MedusaV2Flag.key
)
let priceList = await manager.transaction(async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.create(req.validatedBody as CreatePriceListInput)
})
if (isMedusaV2FlagEnabled) {
const createPriceListWorkflow = createPriceLists(req.scope)
const validatedInput = req.validatedBody as CreatePriceListInput
const rules: PricingTypes.CreatePriceListRules = {}
const customerGroups = validatedInput?.customer_groups || []
delete validatedInput.customer_groups
if (customerGroups.length) {
rules["customer_group_id"] = customerGroups.map((cg) => cg.id)
}
const input = {
price_lists: [
{
...validatedInput,
rules,
},
],
} as WorkflowTypes.PriceListWorkflow.CreatePriceListWorkflowInputDTO
const { result } = await createPriceListWorkflow.run({
input,
context: {
manager,
},
})
priceList = result[0]!.priceList
priceList = await getPriceListPricingModule(priceList.id, {
container: req.scope as MedusaContainer,
})
} else {
priceList = await manager.transaction(async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.create(req.validatedBody as CreatePriceListInput)
})
priceList = await priceListService.retrieve(priceList.id, {
select: defaultAdminPriceListFields as (keyof PriceList)[],
relations: defaultAdminPriceListRelations,
})
}
priceList = await priceListService.retrieve(priceList.id, {
select: defaultAdminPriceListFields as (keyof PriceList)[],
relations: defaultAdminPriceListRelations,
})
res.json({ price_list: priceList })
}

View File

@@ -1,6 +1,3 @@
import { WorkflowTypes } from "@medusajs/types"
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
import { removePriceLists } from "@medusajs/core-flows"
import { EntityManager } from "typeorm"
import PriceListService from "../../../../services/price-list"
@@ -86,34 +83,13 @@ import PriceListService from "../../../../services/price-list"
*/
export default async (req, res) => {
const { id } = req.params
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
const manager: EntityManager = req.scope.resolve("manager")
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
MedusaV2Flag.key
)
if (isMedusaV2FlagEnabled) {
const removePriceListsWorkflow = removePriceLists(req.scope)
const input = {
price_lists: [id],
} as WorkflowTypes.PriceListWorkflow.RemovePriceListWorkflowInputDTO
await removePriceListsWorkflow.run({
input,
context: {
manager,
},
})
} else {
const priceListService: PriceListService =
req.scope.resolve("priceListService")
await manager.transaction(async (transactionManager) => {
await priceListService.withTransaction(transactionManager).delete(id)
})
}
const priceListService: PriceListService =
req.scope.resolve("priceListService")
await manager.transaction(async (transactionManager) => {
await priceListService.withTransaction(transactionManager).delete(id)
})
res.json({
id,

View File

@@ -1,10 +1,7 @@
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
import { ArrayNotEmpty, IsString } from "class-validator"
import { EntityManager } from "typeorm"
import PriceListService from "../../../../services/price-list"
import { validator } from "../../../../utils/validator"
import { WorkflowTypes } from "@medusajs/types"
import { removePriceListPrices } from "@medusajs/core-flows"
/**
* @oas [delete] /admin/price-lists/{id}/prices/batch
@@ -101,43 +98,20 @@ import { removePriceListPrices } from "@medusajs/core-flows"
*/
export default async (req, res) => {
const { id } = req.params
const validated = await validator(
AdminDeletePriceListPricesPricesReq,
req.body
)
const manager: EntityManager = req.scope.resolve("manager")
const priceListService: PriceListService =
req.scope.resolve("priceListService")
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
const manager: EntityManager = req.scope.resolve("manager")
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
MedusaV2Flag.key
)
if (isMedusaV2FlagEnabled) {
const deletePriceListPricesWorkflow = removePriceListPrices(req.scope)
const input = {
price_list_id: id,
money_amount_ids: validated.price_ids,
} as WorkflowTypes.PriceListWorkflow.RemovePriceListPricesWorkflowInputDTO
await deletePriceListPricesWorkflow.run({
input,
context: {
manager,
},
})
} else {
await manager.transaction(async (transactionManager) => {
await priceListService
.withTransaction(transactionManager)
.deletePrices(id, validated.price_ids)
})
}
await manager.transaction(async (transactionManager) => {
await priceListService
.withTransaction(transactionManager)
.deletePrices(id, validated.price_ids)
})
res.json({ ids: validated.price_ids, object: "money-amount", deleted: true })
}

View File

@@ -1,6 +1,3 @@
import { WorkflowTypes } from "@medusajs/types"
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
import { removePriceListProductPrices } from "@medusajs/core-flows"
import { EntityManager } from "typeorm"
import PriceListService from "../../../../services/price-list"
@@ -94,47 +91,18 @@ import PriceListService from "../../../../services/price-list"
*/
export default async (req, res) => {
const { id, product_id } = req.params
const priceListService: PriceListService =
req.scope.resolve("priceListService")
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
const manager: EntityManager = req.scope.resolve("manager")
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
MedusaV2Flag.key
)
let deletedPriceIds: string[] = []
if (isMedusaV2FlagEnabled) {
const deletePriceListProductsWorkflow = removePriceListProductPrices(
req.scope
)
const input = {
product_ids: [product_id],
price_list_id: id,
} as WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO
const { result } = await deletePriceListProductsWorkflow.run({
input,
context: {
manager,
},
})
deletedPriceIds = result
} else {
const [deletedIds] = await manager.transaction(
async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.deleteProductPrices(id, [product_id])
}
)
deletedPriceIds = deletedIds
}
const [deletedIds] = await manager.transaction(async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.deleteProductPrices(id, [product_id])
})
deletedPriceIds = deletedIds
return res.json({
ids: deletedPriceIds,

View File

@@ -1,11 +1,8 @@
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
import { removePriceListProductPrices } from "@medusajs/core-flows"
import { ArrayNotEmpty, IsString } from "class-validator"
import { Request, Response } from "express"
import { EntityManager } from "typeorm"
import PriceListService from "../../../../services/price-list"
import { validator } from "../../../../utils/validator"
import { WorkflowTypes } from "@medusajs/types"
/**
* @oas [delete] /admin/price-lists/{id}/products/prices/batch
@@ -114,43 +111,16 @@ export default async (req: Request, res: Response) => {
const priceListService: PriceListService =
req.scope.resolve("priceListService")
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
const manager: EntityManager = req.scope.resolve("manager")
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
MedusaV2Flag.key
)
let deletedPriceIds: string[] = []
if (isMedusaV2FlagEnabled) {
const deletePriceListProductsWorkflow = removePriceListProductPrices(
req.scope
)
const [deletedIds] = await manager.transaction(async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.deleteProductPrices(id, validated.product_ids)
})
const input = {
product_ids: validated.product_ids,
price_list_id: id,
} as WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO
const { result } = await deletePriceListProductsWorkflow.run({
input,
context: {
manager,
},
})
deletedPriceIds = result
} else {
const [deletedIds] = await manager.transaction(
async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.deleteProductPrices(id, validated.product_ids)
}
)
deletedPriceIds = deletedIds
}
deletedPriceIds = deletedIds
return res.json({
ids: deletedPriceIds,

View File

@@ -1,8 +1,5 @@
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
import { removePriceListVariantPrices } from "@medusajs/core-flows"
import { EntityManager } from "typeorm"
import PriceListService from "../../../../services/price-list"
import { WorkflowTypes } from "@medusajs/types"
/**
* @oas [delete] /admin/price-lists/{id}/variants/{variant_id}/prices
@@ -97,46 +94,16 @@ export default async (req, res) => {
const priceListService: PriceListService =
req.scope.resolve("priceListService")
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
const manager: EntityManager = req.scope.resolve("manager")
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
MedusaV2Flag.key
)
let deletedPriceIds: string[] = []
if (isMedusaV2FlagEnabled) {
const deletePriceListProductsWorkflow = removePriceListVariantPrices(
req.scope
)
const input = {
variant_ids: [variant_id],
price_list_id: id,
} as WorkflowTypes.PriceListWorkflow.RemovePriceListVariantsWorkflowInputDTO
const { result } = await deletePriceListProductsWorkflow.run({
input,
context: {
manager,
},
})
deletedPriceIds = result
} else {
const [deletedIds] = await manager.transaction(
async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.deleteVariantPrices(id, [variant_id])
}
)
deletedPriceIds = deletedIds
}
const [deletedIds] = await manager.transaction(async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.deleteVariantPrices(id, [variant_id])
})
return res.json({
ids: deletedPriceIds,
ids: deletedIds,
object: "money-amount",
deleted: true,
})

View File

@@ -11,13 +11,12 @@ import {
extendedFindParamsMixin,
} from "../../../../types/common"
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
import { Type } from "class-transformer"
import { Request } from "express"
import { ProductStatus } from "../../../../models"
import PriceListService from "../../../../services/price-list"
import { FilterableProductProps } from "../../../../types/product"
import { IsType, listProducts } from "../../../../utils"
import { IsType } from "../../../../utils"
/**
* @oas [get] /admin/price-lists/{id}/products
@@ -228,10 +227,6 @@ import { IsType, listProducts } from "../../../../utils"
export default async (req: Request, res) => {
const { id } = req.params
const { offset, limit } = req.validatedQuery
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
let products
let count
const priceListService: PriceListService =
req.scope.resolve("priceListService")
@@ -240,19 +235,11 @@ export default async (req: Request, res) => {
price_list_id: [id],
}
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
;[products, count] = await listProducts(
req.scope,
filterableFields,
req.listConfig
)
} else {
;[products, count] = await priceListService.listProducts(
id,
filterableFields,
req.listConfig
)
}
const [products, count] = await priceListService.listProducts(
id,
filterableFields,
req.listConfig
)
res.json({
products,

View File

@@ -1,5 +1,4 @@
import { MedusaContainer, PricingTypes, WorkflowTypes } from "@medusajs/types"
import { MedusaV2Flag, PriceListStatus, PriceListType } from "@medusajs/utils"
import { PriceListStatus, PriceListType } from "@medusajs/utils"
import {
IsArray,
IsBoolean,
@@ -10,7 +9,6 @@ import {
} from "class-validator"
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
import { updatePriceLists } from "@medusajs/core-flows"
import { Transform, Type } from "class-transformer"
import { EntityManager } from "typeorm"
import { PriceList } from "../../../.."
@@ -19,7 +17,6 @@ import PriceListService from "../../../../services/price-list"
import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list"
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
import { validator } from "../../../../utils/validator"
import { getPriceListPricingModule } from "./modules-queries"
import { transformOptionalDate } from "../../../../utils/validators/date-transform"
/**
@@ -119,8 +116,6 @@ import { transformOptionalDate } from "../../../../utils/validators/date-transfo
*/
export default async (req, res) => {
const { id } = req.params
let priceList
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
const manager: EntityManager = req.scope.resolve("manager")
const priceListService: PriceListService =
req.scope.resolve("priceListService")
@@ -130,48 +125,16 @@ export default async (req, res) => {
req.body
)
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
const updateVariantsWorkflow = updatePriceLists(req.scope)
const customerGroups = validated.customer_groups
delete validated.customer_groups
await manager.transaction(async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.update(id, validated)
})
const updatePriceListInput = {
id,
...validated,
} as PricingTypes.UpdatePriceListDTO
if (Array.isArray(customerGroups)) {
updatePriceListInput.rules = {
customer_group_id: customerGroups.map((group) => group.id),
}
}
const input = {
price_lists: [updatePriceListInput],
} as WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowInputDTO
await updateVariantsWorkflow.run({
input,
context: {
manager,
},
})
priceList = await getPriceListPricingModule(id, {
container: req.scope as MedusaContainer,
})
} else {
await manager.transaction(async (transactionManager) => {
return await priceListService
.withTransaction(transactionManager)
.update(id, validated)
})
priceList = await priceListService.retrieve(id, {
select: defaultAdminPriceListFields as (keyof PriceList)[],
relations: defaultAdminPriceListRelations,
})
}
const priceList = await priceListService.retrieve(id, {
select: defaultAdminPriceListFields as (keyof PriceList)[],
relations: defaultAdminPriceListRelations,
})
res.json({ price_list: priceList })
}

View File

@@ -6,7 +6,7 @@ import express from "express"
import loaders from "../loaders"
import Logger from "../loaders/logger"
import { PriceList } from "../models"
import { CurrencyService, PriceListService } from "../services"
import { PriceListService } from "../services"
import { createDefaultRuleTypes } from "./utils/create-default-rule-types"
import { migrateProductVariantPricing } from "./utils/migrate-money-amounts-to-pricing-module"
@@ -109,7 +109,7 @@ const migratePriceLists = async (container: AwilixContainer) => {
pricingModuleService.addPriceListPrices(
priceListsToUpdate.map((priceList) => {
return {
priceListId: priceList.id,
price_list_id: priceList.id,
prices: priceList.prices
.filter((price) =>
variantIdPriceSetIdMap.has(price.variants?.[0]?.id)
@@ -136,7 +136,7 @@ const migratePriceLists = async (container: AwilixContainer) => {
pricingModuleService.createPriceLists(
priceListsToCreate.map(
({ name: title, prices, customer_groups, ...priceList }) => {
const createData: PricingTypes.CreatePriceListDTO = {
const createData: any = {
...priceList,
title,
}

View File

@@ -1,3 +1,4 @@
import { Modules } from "@medusajs/modules-sdk"
import {
CreatePriceRuleDTO,
CreatePriceSetDTO,
@@ -5,9 +6,8 @@ import {
PricingTypes,
} from "@medusajs/types"
import { PriceListType } from "@medusajs/utils"
import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
import { seedPriceData } from "../../../__fixtures__/seed-price-data"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
@@ -16,7 +16,7 @@ const defaultRules = {
region_id: ["DE", "DK"],
}
const defaultPriceListPrices: PricingTypes.PriceListPriceDTO[] = [
const defaultPriceListPrices: PricingTypes.CreatePriceListPriceDTO[] = [
{
amount: 232,
currency_code: "PLN",

View File

@@ -1,10 +1,10 @@
import { Modules } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { createPriceLists } from "../../../__fixtures__/price-list"
import { createPriceListRules } from "../../../__fixtures__/price-list-rules"
import { createRuleTypes } from "../../../__fixtures__/rule-type"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
@@ -240,7 +240,7 @@ moduleIntegrationTestRunner({
])
await service.setPriceListRules({
priceListId: "price-list-1",
price_list_id: "price-list-1",
rules: {
sales_channel: "sc-1",
},
@@ -280,7 +280,7 @@ moduleIntegrationTestRunner({
])
await service.setPriceListRules({
priceListId: "price-list-1",
price_list_id: "price-list-1",
rules: {
sales_channel: ["sc-1", "sc-2"],
},
@@ -315,7 +315,7 @@ moduleIntegrationTestRunner({
describe("removePriceListRules", () => {
it("should remove a priceListRule from a priceList", async () => {
await service.removePriceListRules({
priceListId: "price-list-1",
price_list_id: "price-list-1",
rules: ["currency_code"],
})

View File

@@ -1,8 +1,8 @@
import { Modules } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { createPriceLists } from "../../../__fixtures__/price-list"
import { createPriceSets } from "../../../__fixtures__/price-set"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
@@ -666,7 +666,7 @@ moduleIntegrationTestRunner({
it("should add a price to a priceList successfully", async () => {
await service.addPriceListPrices([
{
priceListId: "price-list-1",
price_list_id: "price-list-1",
prices: [
{
amount: 123,
@@ -734,7 +734,7 @@ moduleIntegrationTestRunner({
try {
await service.addPriceListPrices([
{
priceListId: "price-list-1",
price_list_id: "price-list-1",
prices: [
{
amount: 123,
@@ -774,7 +774,7 @@ moduleIntegrationTestRunner({
await service.addPriceListPrices([
{
priceListId: "price-list-1",
price_list_id: "price-list-1",
prices: [
{
amount: 123,
@@ -843,6 +843,203 @@ moduleIntegrationTestRunner({
)
})
})
describe("updatePriceListPrices", () => {
it("should update a price to a priceList successfully", async () => {
const [priceSet] = await service.create([
{
rules: [
{ rule_attribute: "region_id" },
{ rule_attribute: "customer_group_id" },
],
},
])
await service.addPriceListPrices([
{
price_list_id: "price-list-1",
prices: [
{
id: "test-price-id",
amount: 123,
currency_code: "EUR",
price_set_id: priceSet.id,
rules: {
region_id: "test",
},
} as any,
],
},
])
await service.updatePriceListPrices([
{
price_list_id: "price-list-1",
prices: [
{
id: "test-price-id",
price_set_id: priceSet.id,
rules: {
region_id: "new test",
customer_group_id: "new test",
},
},
],
},
])
const [priceList] = await service.listPriceLists(
{ id: ["price-list-1"] },
{
relations: [
"price_set_money_amounts.money_amount",
"price_set_money_amounts.price_set",
"price_set_money_amounts.price_rules",
"price_set_money_amounts.price_rules.rule_type",
"price_list_rules.price_list_rule_values",
"price_list_rules.rule_type",
],
select: [
"id",
"price_set_money_amounts.price_rules.value",
"price_set_money_amounts.price_rules.rule_type.rule_attribute",
"price_set_money_amounts.rules_count",
"price_set_money_amounts.money_amount.amount",
"price_set_money_amounts.money_amount.currency_code",
"price_set_money_amounts.money_amount.price_list_id",
"price_list_rules.price_list_rule_values.value",
"price_list_rules.rule_type.rule_attribute",
],
}
)
expect(priceList).toEqual(
expect.objectContaining({
id: expect.any(String),
price_set_money_amounts: expect.arrayContaining([
expect.objectContaining({
rules_count: 2,
price_rules: expect.arrayContaining([
expect.objectContaining({
value: "new test",
rule_type: expect.objectContaining({
rule_attribute: "region_id",
}),
}),
expect.objectContaining({
value: "new test",
rule_type: expect.objectContaining({
rule_attribute: "customer_group_id",
}),
}),
]),
price_list: expect.objectContaining({
id: expect.any(String),
}),
money_amount: expect.objectContaining({
amount: 123,
currency_code: "EUR",
}),
}),
]),
price_list_rules: [],
})
)
})
it("should fail to add a price with non-existing rule-types in the price-set to a priceList", async () => {
await service.createRuleTypes([
{ name: "twitter_handle", rule_attribute: "twitter_handle" },
{ name: "region_id", rule_attribute: "region_id" },
])
const [priceSet] = await service.create([
{ rules: [{ rule_attribute: "region_id" }] },
])
await service.addPriceListPrices([
{
price_list_id: "price-list-1",
prices: [
{
id: "test-price-id",
amount: 123,
currency_code: "EUR",
price_set_id: priceSet.id,
rules: { region_id: "test" },
} as any,
],
},
])
const error = await service
.updatePriceListPrices([
{
price_list_id: "price-list-1",
prices: [
{
id: "test-price-id",
amount: 123,
price_set_id: priceSet.id,
rules: { twitter_handle: "owjuhl" },
},
],
},
])
.catch((e) => e)
expect(error.message).toEqual(
`Invalid rule type configuration: Price set rules doesn't exist for rule_attribute "twitter_handle" in price set ${priceSet.id}`
)
})
})
describe("removePrices", () => {
it("should remove prices from a priceList successfully", async () => {
const [priceSet] = await service.create([
{ rules: [{ rule_attribute: "region_id" }] },
])
await service.addPriceListPrices([
{
price_list_id: "price-list-1",
prices: [
{
amount: 123,
currency_code: "EUR",
price_set_id: priceSet.id,
},
],
},
])
let [priceList] = await service.listPriceLists(
{ id: ["price-list-1"] },
{
relations: ["price_set_money_amounts"],
select: ["price_set_money_amounts.id"],
}
)
await service.removePrices(
priceList.price_set_money_amounts!.map((psma) => psma.id)
)
;[priceList] = await service.listPriceLists(
{ id: ["price-list-1"] },
{
relations: ["price_set_money_amounts"],
select: ["id", "price_set_money_amounts.id"],
}
)
expect(priceList).toEqual(
expect.objectContaining({
id: expect.any(String),
price_set_money_amounts: [],
})
)
})
})
})
},
})

View File

@@ -5,10 +5,10 @@ import {
} from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { PriceSetRuleType } from "../../../../src"
import { seedPriceData } from "../../../__fixtures__/seed-price-data"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { PriceSetRuleType } from "../../../../src"
import { seedPriceData } from "../../../__fixtures__/seed-price-data"
jest.setTimeout(30000)

View File

@@ -1031,9 +1031,7 @@ export default class PricingModuleService<
.flat()
const existingPriceListRules = await this.listPriceListRules(
{
id: priceListRuleIds,
},
{ id: priceListRuleIds },
{},
sharedContext
)
@@ -1046,9 +1044,7 @@ export default class PricingModuleService<
}
const ruleTypes = await this.listRuleTypes(
{
rule_attribute: ruleAttributes,
},
{ rule_attribute: ruleAttributes },
{ take: null },
sharedContext
)
@@ -1059,6 +1055,7 @@ export default class PricingModuleService<
for (const priceListData of data) {
const { rules, ...priceListOnlyData } = priceListData
const updatePriceListData: any = {
...priceListOnlyData,
}
@@ -1083,12 +1080,7 @@ export default class PricingModuleService<
if (!ruleType) {
;[ruleType] = await this.createRuleTypes(
[
{
name: ruleAttribute,
rule_attribute: ruleAttribute,
},
],
[{ name: ruleAttribute, rule_attribute: ruleAttribute }],
sharedContext
)
@@ -1107,12 +1099,7 @@ export default class PricingModuleService<
for (const ruleValue of ruleValues as string[]) {
await this.priceListRuleValueService_.create(
[
{
price_list_rule: priceListRule,
value: ruleValue,
},
],
[{ price_list_rule: priceListRule, value: ruleValue }],
sharedContext
)
}
@@ -1161,6 +1148,185 @@ export default class PricingModuleService<
})
}
@InjectManager("baseRepository_")
async updatePriceListPrices(
data: PricingTypes.UpdatePriceListPricesDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.PriceListDTO[]> {
return await this.updatePriceListPrices_(data, sharedContext)
}
@InjectTransactionManager("baseRepository_")
protected async updatePriceListPrices_(
data: PricingTypes.UpdatePriceListPricesDTO[],
sharedContext: Context = {}
): Promise<PricingTypes.PriceListDTO[]> {
const ruleTypeAttributes: string[] = []
const priceListIds: string[] = []
const moneyAmountIds: string[] = []
const priceSetIds = data
.map((d) => d.prices.map((price) => price.price_set_id))
.flat()
for (const priceListData of data) {
priceListIds.push(priceListData.price_list_id)
for (const price of priceListData.prices) {
moneyAmountIds.push(price.id)
ruleTypeAttributes.push(...Object.keys(price.rules || {}))
}
}
const moneyAmounts = await this.listMoneyAmounts(
{ id: moneyAmountIds },
{
take: null,
relations: [
"price_set_money_amount",
"price_set_money_amount.price_rules",
],
},
sharedContext
)
const moneyAmountMap: Map<string, PricingTypes.MoneyAmountDTO> = new Map(
moneyAmounts.map((ma) => [ma.id, ma])
)
const ruleTypes = await this.listRuleTypes(
{ rule_attribute: ruleTypeAttributes },
{ take: null },
sharedContext
)
const ruleTypeMap: Map<string, RuleTypeDTO> = new Map(
ruleTypes.map((rt) => [rt.rule_attribute, rt])
)
const priceSets = await this.list(
{ id: priceSetIds },
{ relations: ["rule_types"] },
sharedContext
)
const priceSetRuleTypeMap: Map<string, Set<string>> = priceSets.reduce(
(acc, curr) => {
const priceSetRuleAttributeSet: Set<string> =
acc.get(curr.id) || new Set()
for (const rt of curr.rule_types ?? []) {
priceSetRuleAttributeSet.add(rt.rule_attribute)
}
acc.set(curr.id, priceSetRuleAttributeSet)
return acc
},
new Map()
)
const ruleTypeErrors: string[] = []
for (const priceListData of data) {
for (const price of priceListData.prices) {
for (const ruleAttribute of Object.keys(price.rules ?? {})) {
if (
!priceSetRuleTypeMap.get(price.price_set_id)?.has(ruleAttribute)
) {
ruleTypeErrors.push(
`rule_attribute "${ruleAttribute}" in price set ${price.price_set_id}`
)
}
}
}
}
if (ruleTypeErrors.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Invalid rule type configuration: Price set rules doesn't exist for ${ruleTypeErrors.join(
", "
)}`
)
}
const priceLists = await this.listPriceLists(
{ id: priceListIds },
{ take: null },
sharedContext
)
const priceListMap = new Map(priceLists.map((p) => [p.id, p]))
for (const { price_list_id: priceListId, prices } of data) {
const priceList = priceListMap.get(priceListId)
if (!priceList) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Price list with id: ${priceListId} not found`
)
}
const moneyAmountsToUpdate: PricingTypes.UpdateMoneyAmountDTO[] = []
const priceRulesToDelete: string[] = []
const priceRulesToCreate: PricingTypes.CreatePriceRuleDTO[] = []
const psmaToUpdate: ServiceTypes.UpdatePriceSetMoneyAmountDTO[] = []
for (const price of prices) {
const { rules, price_set_id, ...priceData } = price
const moneyAmount = moneyAmountMap.get(price.id)!
const priceSetMoneyAmount = moneyAmount.price_set_money_amount!
const priceRules = priceSetMoneyAmount.price_rules!
moneyAmountsToUpdate.push(priceData)
if (typeof rules === "undefined") {
continue
}
psmaToUpdate.push({
id: priceSetMoneyAmount!.id,
rules_count: Object.keys(rules).length,
})
priceRulesToDelete.push(...priceRules.map((pr) => pr.id))
priceRulesToCreate.push(
...Object.entries(rules).map(([ruleAttribute, ruleValue]) => ({
price_set_id: price.price_set_id,
rule_type_id: ruleTypeMap.get(ruleAttribute)!.id,
value: ruleValue,
price_set_money_amount_id: priceSetMoneyAmount.id,
}))
)
}
await Promise.all([
this.moneyAmountService_.update(moneyAmountsToUpdate),
this.priceRuleService_.delete(priceRulesToDelete),
this.priceRuleService_.create(priceRulesToCreate),
this.priceSetMoneyAmountService_.update(psmaToUpdate),
])
}
return priceLists
}
@InjectManager("baseRepository_")
async removePrices(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.removePrices_(ids, sharedContext)
}
@InjectTransactionManager("baseRepository_")
protected async removePrices_(
ids: string[],
sharedContext: Context = {}
): Promise<void> {
await this.priceSetMoneyAmountService_.delete(ids, sharedContext)
}
@InjectManager("baseRepository_")
async addPriceListPrices(
data: PricingTypes.AddPriceListPricesDTO[],
@@ -1179,7 +1345,7 @@ export default class PricingModuleService<
const priceSetIds: string[] = []
for (const priceListData of data) {
priceListIds.push(priceListData.priceListId)
priceListIds.push(priceListData.price_list_id)
for (const price of priceListData.prices) {
ruleTypeAttributes.push(...Object.keys(price.rules || {}))
@@ -1251,7 +1417,7 @@ export default class PricingModuleService<
const priceListMap = new Map(priceLists.map((p) => [p.id, p]))
for (const { priceListId, prices } of data) {
for (const { price_list_id: priceListId, prices } of data) {
const priceList = priceListMap.get(priceListId)
if (!priceList) {
@@ -1322,7 +1488,7 @@ export default class PricingModuleService<
sharedContext: Context = {}
): Promise<PricingTypes.PriceListDTO[]> {
const priceLists = await this.priceListService_.list(
{ id: data.map((d) => d.priceListId) },
{ id: data.map((d) => d.price_list_id) },
{
relations: ["price_list_rules", "price_list_rules.rule_type"],
},
@@ -1347,7 +1513,7 @@ export default class PricingModuleService<
const priceRuleValues = new Map<string, Map<string, string[]>>()
for (const { priceListId, rules } of data) {
for (const { price_list_id: priceListId, rules } of data) {
const priceList = priceListMap.get(priceListId)
if (!priceList) {
@@ -1465,7 +1631,7 @@ export default class PricingModuleService<
sharedContext: Context = {}
): Promise<PricingTypes.PriceListDTO[]> {
const priceLists = await this.priceListService_.list(
{ id: data.map((d) => d.priceListId) },
{ id: data.map((d) => d.price_list_id) },
{
relations: ["price_list_rules", "price_list_rules.rule_type"],
},
@@ -1475,7 +1641,7 @@ export default class PricingModuleService<
const priceListMap = new Map(priceLists.map((p) => [p.id, p]))
const idsToDelete: string[] = []
for (const { priceListId, rules } of data) {
for (const { price_list_id: priceListId, rules } of data) {
const priceList = priceListMap.get(priceListId)
if (!priceList) {

View File

@@ -3,8 +3,8 @@ import { PriceListStatus, PriceListType } from "@medusajs/utils"
export interface CreatePriceListDTO {
title: string
description: string
starts_at?: Date | string | null
ends_at?: Date | string | null
starts_at?: string | null
ends_at?: string | null
status?: PriceListStatus
type?: PriceListType
rules_count?: number
@@ -13,8 +13,8 @@ export interface CreatePriceListDTO {
export interface UpdatePriceListDTO {
id: string
title?: string
starts_at?: Date | string | null
ends_at?: Date | string | null
starts_at?: string | null
ends_at?: string | null
status?: PriceListStatus
number_rules?: number
}

View File

@@ -18,10 +18,10 @@ export interface UpdateMoneyAmountDTO {
export interface MoneyAmountDTO {
id: string
currency_code?: string
amount?: number
min_quantity?: number
max_quantity?: number
currency_code?: string | null
amount?: number | null
min_quantity?: number | null
max_quantity?: number | null
price_set_money_amount?: PriceSetMoneyAmountDTO
created_at: Date
updated_at: Date

View File

@@ -1,4 +1,3 @@
import { PriceListStatus, PriceListType } from "@medusajs/utils"
import {
BaseFilterable,
MoneyAmountDTO,
@@ -6,12 +5,13 @@ import {
PriceSetMoneyAmountDTO,
RuleTypeDTO,
} from "@medusajs/types"
import { PriceListStatus, PriceListType } from "@medusajs/utils"
export interface CreatePriceListDTO {
title: string
description: string
starts_at?: Date | string | null
ends_at?: Date | string | null
starts_at?: string | null
ends_at?: string | null
status?: PriceListStatus
type?: PriceListType
number_rules?: number
@@ -20,8 +20,8 @@ export interface CreatePriceListDTO {
export interface UpdatePriceListDTO {
id: string
title?: string
starts_at?: Date | string | null
ends_at?: Date | string | null
starts_at?: string | null
ends_at?: string | null
status?: PriceListStatus
number_rules?: number
}

View File

@@ -11,6 +11,7 @@ export interface UpdatePriceSetMoneyAmountDTO {
title?: string
price_set?: PriceSetDTO
money_amount?: MoneyAmountDTO
rules_count?: number
}
export interface CreatePriceSetMoneyAmountDTO {

View File

@@ -86,7 +86,7 @@ export interface UpdateMoneyAmountDTO {
/**
* The code of the currency to associate with the money amount.
*/
currency_code?: string
currency_code?: string | null
/**
* The price of this money amount.
*/

View File

@@ -1,8 +1,11 @@
import { CreateMoneyAmountDTO, MoneyAmountDTO } from "./money-amount";
import { BaseFilterable } from "../../dal";
import { PriceSetMoneyAmountDTO } from "./price-set-money-amount";
import { RuleTypeDTO } from "./rule-type";
import { BaseFilterable } from "../../dal"
import {
CreateMoneyAmountDTO,
MoneyAmountDTO,
UpdateMoneyAmountDTO,
} from "./money-amount"
import { PriceSetMoneyAmountDTO } from "./price-set-money-amount"
import { RuleTypeDTO } from "./rule-type"
/**
* @enum
@@ -106,7 +109,18 @@ export interface PriceListDTO {
*
* The prices associated with a price list.
*/
export interface PriceListPriceDTO extends CreateMoneyAmountDTO {
export interface CreatePriceListPriceDTO extends CreateMoneyAmountDTO {
/**
* The ID of the associated price set.
*/
price_set_id: string
/**
* The rules to add to the price. The object's keys are rule types' `rule_attribute` attribute, and values are the value of that rule associated with this price.
*/
rules?: CreatePriceSetPriceRules
}
export interface UpdatePriceListPriceDTO extends UpdateMoneyAmountDTO {
/**
* The ID of the associated price set.
*/
@@ -152,11 +166,11 @@ export interface CreatePriceListDTO {
/**
* The price list is enabled starting from this date.
*/
starts_at?: Date | string | null
starts_at?: string | null
/**
* The price list expires after this date.
*/
ends_at?: Date | string | null
ends_at?: string | null
/**
* The price list's status.
*/
@@ -176,7 +190,7 @@ export interface CreatePriceListDTO {
/**
* The prices associated with the price list.
*/
prices?: PriceListPriceDTO[]
prices?: CreatePriceListPriceDTO[]
}
/**
@@ -200,11 +214,11 @@ export interface UpdatePriceListDTO {
/**
* The price list is enabled starting from this date.
*/
starts_at?: Date | string | null
starts_at?: string | null
/**
* The price list expires after this date.
*/
ends_at?: Date | string | null
ends_at?: string | null
/**
* The price list's status.
*/
@@ -412,11 +426,27 @@ export interface AddPriceListPricesDTO {
/**
* The ID of the price list to add prices to.
*/
priceListId: string
price_list_id: string
/**
* The prices to add.
*/
prices: PriceListPriceDTO[]
prices: CreatePriceListPriceDTO[]
}
/**
* @interface
*
* The prices to be added to a price list.
*/
export interface UpdatePriceListPricesDTO {
/**
* The ID of the price list to add prices to.
*/
price_list_id: string
/**
* The prices to add.
*/
prices: UpdatePriceListPriceDTO[]
}
/**
@@ -428,7 +458,7 @@ export interface SetPriceListRulesDTO {
/**
* The ID of the price list to add rules to.
*/
priceListId: string
price_list_id: string
/**
* The rules to add to the price list. Each key of the object is a rule type's `rule_attribute`, and its value
* is the value(s) of the rule.
@@ -445,7 +475,7 @@ export interface RemovePriceListRulesDTO {
/**
* The ID of the price list to remove rules from.
*/
priceListId: string
price_list_id: string
/**
* The rules to remove from the price list. Each item being a rule type's `rule_attribute`.
*/

View File

@@ -1,6 +1,5 @@
import { BaseFilterable } from "../../dal"
import { PriceSetDTO } from "./price-set"
import { PriceSetMoneyAmountDTO } from "./price-set-money-amount"
import { RuleTypeDTO } from "./rule-type"
/**
@@ -73,18 +72,10 @@ export interface CreatePriceRuleDTO {
* The ID of the associated price set.
*/
price_set_id?: string
/**
* The ID or object of the associated price set.
*/
price_set?: string | PriceSetDTO
/**
* The ID of the associated rule type.
*/
rule_type_id?: string
/**
* The ID of the associated rule type.
*/
rule_type?: string | RuleTypeDTO
/**
* The value of the price rule.
*/
@@ -97,10 +88,6 @@ export interface CreatePriceRuleDTO {
* The ID of the associated price set money amount.
*/
price_set_money_amount_id?: string
/**
* The ID or object of the associated price set money amount.
*/
price_set_money_amount?: string | PriceSetMoneyAmountDTO
}
/**

View File

@@ -1,8 +1,8 @@
import { BaseFilterable } from "../../dal";
import { MoneyAmountDTO } from "./money-amount";
import { PriceListDTO } from "./price-list";
import { PriceRuleDTO } from "./price-rule";
import { PriceSetDTO } from "./price-set";
import { BaseFilterable } from "../../dal"
import { MoneyAmountDTO } from "./money-amount"
import { PriceListDTO } from "./price-list"
import { PriceRuleDTO } from "./price-rule"
import { PriceSetDTO } from "./price-set"
/**
* @interface

View File

@@ -1,2 +1,3 @@
export * from "./common"
export * from "./service"
export * from "./workflows"

View File

@@ -33,6 +33,7 @@ import {
SetPriceListRulesDTO,
UpdateMoneyAmountDTO,
UpdatePriceListDTO,
UpdatePriceListPricesDTO,
UpdatePriceListRuleDTO,
UpdatePriceRuleDTO,
UpdatePriceSetDTO,
@@ -3397,6 +3398,11 @@ export interface IPricingModuleService extends IModuleService {
sharedContext?: Context
): Promise<PriceListDTO[]>
updatePriceListPrices(
data: UpdatePriceListPricesDTO[],
sharedContext?: Context
): Promise<PriceListDTO[]>
/**
* This method is used to set the rules of a price list.
*
@@ -3454,4 +3460,6 @@ export interface IPricingModuleService extends IModuleService {
data: RemovePriceListRulesDTO,
sharedContext?: Context
): Promise<PriceListDTO>
removePrices(ids: string[], sharedContext?: Context): Promise<void>
}

View File

@@ -0,0 +1,41 @@
import { PriceListStatus } from "./common"
export interface CreatePriceListPriceWorkflowDTO {
amount: number
currency_code: string
variant_id: string
max_quantity?: number
min_quantity?: number
rules?: Record<string, string>
}
export interface UpdatePriceListPriceWorkflowDTO {
id: string
amount?: number
currency_code?: string
variant_id?: string
max_quantity?: number
min_quantity?: number
rules?: Record<string, string>
}
export interface CreatePriceListWorkflowInputDTO {
title: string
description: string
starts_at?: string | null
ends_at?: string | null
status?: PriceListStatus
rules?: Record<string, string[]>
prices?: CreatePriceListPriceWorkflowDTO[]
}
export interface UpdatePriceListWorkflowInputDTO {
id: string
title?: string
description?: string
starts_at?: string | null
ends_at?: string | null
status?: PriceListStatus
rules?: Record<string, string[]>
prices?: (UpdatePriceListPriceWorkflowDTO | CreatePriceListPriceWorkflowDTO)[]
}

View File

@@ -3,6 +3,9 @@ import { isObject } from "./is-object"
export function getSelectsAndRelationsFromObjectArray(
dataArray: object[],
options: { objectFields: string[] } = {
objectFields: [],
},
prefix?: string
): {
selects: string[]
@@ -13,10 +16,11 @@ export function getSelectsAndRelationsFromObjectArray(
for (const data of dataArray) {
for (const [key, value] of Object.entries(data)) {
if (isObject(value)) {
if (isObject(value) && !options.objectFields.includes(key)) {
relations.push(setKey(key, prefix))
const res = getSelectsAndRelationsFromObjectArray(
[value],
options,
setKey(key, prefix)
)
selects.push(...res.selects)
@@ -25,6 +29,7 @@ export function getSelectsAndRelationsFromObjectArray(
relations.push(setKey(key, prefix))
const res = getSelectsAndRelationsFromObjectArray(
value,
options,
setKey(key, prefix)
)
selects.push(...res.selects)

View File

@@ -0,0 +1,70 @@
import {
PriceListRuleDTO,
PriceRuleDTO,
PriceSetMoneyAmountDTO,
ProductVariantDTO,
UpdatePriceListPriceDTO,
} from "@medusajs/types"
export function buildPriceListRules(
priceListRules: PriceListRuleDTO[]
): Record<string, string[]> {
return priceListRules.reduce((acc, curr) => {
const ruleAttribute = curr.rule_type.rule_attribute
const ruleValues = curr.price_list_rule_values || []
acc[ruleAttribute] = ruleValues.map((ruleValue) => ruleValue.value)
return acc
}, {})
}
export function buildPriceSetRules(
priceRules: PriceRuleDTO[]
): Record<string, string> {
return priceRules.reduce((acc, curr) => {
const ruleAttribute = curr.rule_type.rule_attribute
const ruleValue = curr.value
acc[ruleAttribute] = ruleValue
return acc
}, {})
}
export function buildPriceSetPricesForCore(
priceSetMoneyAmounts: (PriceSetMoneyAmountDTO & {
price_set: PriceSetMoneyAmountDTO["price_set"] & {
variant?: ProductVariantDTO
}
})[]
): Record<string, any>[] {
return priceSetMoneyAmounts.map((priceSetMoneyAmount) => {
const productVariant = (priceSetMoneyAmount.price_set as any).variant
const rules: Record<string, string> = priceSetMoneyAmount.price_rules
? buildPriceSetRules(priceSetMoneyAmount.price_rules)
: {}
return {
...priceSetMoneyAmount.money_amount,
variant_id: productVariant?.id ?? null,
rules,
}
})
}
export function buildPriceSetPricesForModule(
priceSetMoneyAmounts: PriceSetMoneyAmountDTO[]
): UpdatePriceListPriceDTO[] {
return priceSetMoneyAmounts.map((priceSetMoneyAmount) => {
const rules: Record<string, string> = priceSetMoneyAmount.price_rules
? buildPriceSetRules(priceSetMoneyAmount.price_rules)
: {}
return {
...priceSetMoneyAmount.money_amount!,
price_set_id: priceSetMoneyAmount.price_set!?.id!,
rules,
}
})
}

View File

@@ -1,2 +1,3 @@
export * from "./builders"
export * from "./price-list"
export * from "./rule-type"

View File

@@ -2,9 +2,9 @@ import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { isString, OrchestrationUtils } from "@medusajs/utils"
import { OrchestrationUtils, isString } from "@medusajs/utils"
import { ulid } from "ulid"
import { resolveValue, StepResponse } from "./helpers"
import { StepResponse, resolveValue } from "./helpers"
import { proxify } from "./helpers/proxy"
import {
CreateWorkflowComposerContext,

View File

@@ -1,5 +1,5 @@
import { OrchestrationUtils } from "@medusajs/utils"
import { PermanentStepFailureError } from "@medusajs/orchestration"
import { OrchestrationUtils } from "@medusajs/utils"
/**
* This class is used to create the response returned by a step. A step return its data by returning an instance of `StepResponse`.
@@ -40,7 +40,7 @@ export class StepResponse<TOutput, TCompensateInput = TOutput> {
* Creates a StepResponse that indicates that the step has failed and the retry mechanism should not kick in anymore.
*
* @param message - An optional message to be logged.
*
*
* @example
* import { Product } from "@medusajs/medusa"
* import {
@@ -48,11 +48,11 @@ export class StepResponse<TOutput, TCompensateInput = TOutput> {
* StepResponse,
* createWorkflow
* } from "@medusajs/workflows-sdk"
*
*
* interface CreateProductInput {
* title: string
* }
*
*
* export const createProductStep = createStep(
* "createProductStep",
* async function (
@@ -62,7 +62,7 @@ export class StepResponse<TOutput, TCompensateInput = TOutput> {
* const productService = context.container.resolve(
* "productService"
* )
*
*
* try {
* const product = await productService.create(input)
* return new StepResponse({
@@ -75,22 +75,22 @@ export class StepResponse<TOutput, TCompensateInput = TOutput> {
* }
* }
* )
*
*
* interface WorkflowInput {
* title: string
* }
*
*
* const myWorkflow = createWorkflow<
* WorkflowInput,
* Product
* >("my-workflow", (input) => {
* // Everything here will be executed and resolved later
* // during the execution. Including the data access.
*
*
* const product = createProductStep(input)
* }
* )
*
*
* myWorkflow()
* .run({
* input: {