feat(pricing,medusa,utils): added list + get endpoints for price lists (#6592)
what: - brings back price list GET endpoints to v2 endpoints
This commit is contained in:
1 parent
531e9e1e94
commit
000eb61e33
23 files changed
+660
-85
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/pricing": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(pricing,medusa,utils): added list + get endpoints for price lists
|
||||||
@@ -2523,6 +2523,9 @@ describe("/admin/orders", () => {
|
|||||||
refundable_amount: 10000,
|
refundable_amount: 10000,
|
||||||
gift_card_total: 0,
|
gift_card_total: 0,
|
||||||
gift_card_tax_total: 0,
|
gift_card_tax_total: 0,
|
||||||
|
items: [{ refundable: 7200 }],
|
||||||
|
claims: [],
|
||||||
|
swaps: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,8 @@ describe("/store/carts", () => {
|
|||||||
"/store/orders/order_test?fields=status&expand=billing_address"
|
"/store/orders/order_test?fields=status&expand=billing_address"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(Object.keys(response.data.order)).toEqual([
|
expect(Object.keys(response.data.order).sort()).toEqual(
|
||||||
|
[
|
||||||
// fields
|
// fields
|
||||||
"status",
|
"status",
|
||||||
|
|
||||||
@@ -310,7 +311,8 @@ describe("/store/carts", () => {
|
|||||||
"gift_card_tax_total",
|
"gift_card_tax_total",
|
||||||
"item_tax_total",
|
"item_tax_total",
|
||||||
"shipping_tax_total",
|
"shipping_tax_total",
|
||||||
])
|
].sort()
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("looks up order", async () => {
|
it("looks up order", async () => {
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import {
|
||||||
|
ICustomerModuleService,
|
||||||
|
IPricingModuleService,
|
||||||
|
IProductModuleService,
|
||||||
|
IRegionModuleService,
|
||||||
|
PriceListStatus,
|
||||||
|
PriceListType,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||||
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
|
||||||
|
|
||||||
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
const adminHeaders = {
|
||||||
|
headers: { "x-medusa-access-token": "test_token" },
|
||||||
|
}
|
||||||
|
|
||||||
|
medusaIntegrationTestRunner({
|
||||||
|
env,
|
||||||
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
|
describe("Admin: Price Lists API", () => {
|
||||||
|
let appContainer
|
||||||
|
let product
|
||||||
|
let variant
|
||||||
|
let region
|
||||||
|
let customerGroup
|
||||||
|
let pricingModule: IPricingModuleService
|
||||||
|
let productModule: IProductModuleService
|
||||||
|
let customerModule: ICustomerModuleService
|
||||||
|
let regionModule: IRegionModuleService
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
appContainer = getContainer()
|
||||||
|
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
|
||||||
|
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
|
||||||
|
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
|
||||||
|
regionModule = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
customerGroup = await customerModule.createCustomerGroup({
|
||||||
|
name: "VIP",
|
||||||
|
})
|
||||||
|
region = await regionModule.create({ name: "US", currency_code: "USD" })
|
||||||
|
;[product] = await productModule.create([{ title: "test product" }])
|
||||||
|
|
||||||
|
await pricingModule.createRuleTypes([
|
||||||
|
{ name: "Customer Group ID", rule_attribute: "customer_group_id" },
|
||||||
|
{ name: "Region ID", rule_attribute: "region_id" },
|
||||||
|
])
|
||||||
|
|
||||||
|
const [productOption] = await productModule.createOptions([
|
||||||
|
{ title: "Test option 1", product_id: product.id },
|
||||||
|
])
|
||||||
|
|
||||||
|
;[variant] = await productModule.createVariants([
|
||||||
|
{
|
||||||
|
product_id: product.id,
|
||||||
|
title: "test product variant",
|
||||||
|
options: [{ value: "test", option_id: productOption.id }],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("GET /admin/price-lists", () => {
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
await pricingModule.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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
customer_group_id: [customerGroup.id],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
let response = await api.get(
|
||||||
|
`/admin/price-lists?fields=id,created_at,customer_groups.id,customer_groups.name,prices.id,prices.currency_code,prices.amount,prices.min_quantity,prices.max_quantity,prices.region_id,prices.variant_id`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.count).toEqual(1)
|
||||||
|
expect(response.data.price_lists).toEqual([
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
created_at: expect.any(String),
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 5000,
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
variant_id: expect.any(String),
|
||||||
|
region_id: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customer_groups: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "VIP",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
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([
|
||||||
|
{
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("GET /admin/price-lists/:id", () => {
|
||||||
|
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 pricingModule.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 pricingModule.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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
let 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),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await api.get(
|
||||||
|
`/admin/price-lists/${priceList.id}?fields=id,prices.id,prices.amount`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data.price_list).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 5000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -56,6 +56,9 @@ export const ProductVariantPriceSet: ModuleJoinerConfig = {
|
|||||||
foreignKey: "id",
|
foreignKey: "id",
|
||||||
alias: "variant_link",
|
alias: "variant_link",
|
||||||
},
|
},
|
||||||
|
fieldAlias: {
|
||||||
|
variant: "variant_link.variant",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { MedusaError } from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../../types/routing"
|
||||||
|
import { listPriceLists } from "../utils"
|
||||||
|
|
||||||
|
export const GET = async (
|
||||||
|
req: AuthenticatedMedusaRequest,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const id = req.params.id
|
||||||
|
const [[priceList], count] = await listPriceLists({
|
||||||
|
container: req.scope,
|
||||||
|
fields: req.retrieveConfig.select!,
|
||||||
|
variables: {
|
||||||
|
filters: { id },
|
||||||
|
skip: 0,
|
||||||
|
take: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Price list with id: ${id} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ price_list: priceList })
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { transformQuery } from "../../../api/middlewares"
|
||||||
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
|
import * as QueryConfig from "./query-config"
|
||||||
|
import {
|
||||||
|
AdminGetPriceListsParams,
|
||||||
|
AdminGetPriceListsPriceListParams,
|
||||||
|
} from "./validators"
|
||||||
|
|
||||||
|
export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/price-lists",
|
||||||
|
middlewares: [
|
||||||
|
transformQuery(
|
||||||
|
AdminGetPriceListsParams,
|
||||||
|
QueryConfig.adminListTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/price-lists/:id",
|
||||||
|
middlewares: [
|
||||||
|
transformQuery(
|
||||||
|
AdminGetPriceListsPriceListParams,
|
||||||
|
QueryConfig.adminRetrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
export enum PriceListRelations {
|
||||||
|
CUSTOMER_GROUPS = "customer_groups",
|
||||||
|
PRICES = "prices",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const priceListRemoteQueryFields = {
|
||||||
|
fields: [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"description",
|
||||||
|
"title",
|
||||||
|
"status",
|
||||||
|
"starts_at",
|
||||||
|
"ends_at",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"deleted_at",
|
||||||
|
],
|
||||||
|
pricesFields: [
|
||||||
|
"price_set_money_amounts.money_amount.id",
|
||||||
|
"price_set_money_amounts.money_amount.currency_code",
|
||||||
|
"price_set_money_amounts.money_amount.amount",
|
||||||
|
"price_set_money_amounts.money_amount.min_quantity",
|
||||||
|
"price_set_money_amounts.money_amount.max_quantity",
|
||||||
|
"price_set_money_amounts.money_amount.created_at",
|
||||||
|
"price_set_money_amounts.money_amount.deleted_at",
|
||||||
|
"price_set_money_amounts.money_amount.updated_at",
|
||||||
|
"price_set_money_amounts.price_set.variant.id",
|
||||||
|
"price_set_money_amounts.price_rules.value",
|
||||||
|
"price_set_money_amounts.price_rules.rule_type.rule_attribute",
|
||||||
|
],
|
||||||
|
customerGroupsFields: [
|
||||||
|
"price_list_rules.price_list_rule_values.value",
|
||||||
|
"price_list_rules.rule_type.rule_attribute",
|
||||||
|
"price_set_money_amounts.price_rules.value",
|
||||||
|
"price_set_money_amounts.price_rules.rule_type.rule_attribute",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultAdminPriceListFields = [
|
||||||
|
...priceListRemoteQueryFields.fields,
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const defaultAdminPriceListRelations = []
|
||||||
|
|
||||||
|
export const allowedAdminPriceListRelations = [
|
||||||
|
PriceListRelations.CUSTOMER_GROUPS,
|
||||||
|
PriceListRelations.PRICES,
|
||||||
|
]
|
||||||
|
|
||||||
|
export const adminListTransformQueryConfig = {
|
||||||
|
defaultLimit: 50,
|
||||||
|
defaultFields: defaultAdminPriceListFields,
|
||||||
|
defaultRelations: defaultAdminPriceListRelations,
|
||||||
|
allowedRelations: allowedAdminPriceListRelations,
|
||||||
|
isList: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const adminRetrieveTransformQueryConfig = {
|
||||||
|
defaultFields: defaultAdminPriceListFields,
|
||||||
|
defaultRelations: defaultAdminPriceListRelations,
|
||||||
|
allowedRelations: allowedAdminPriceListRelations,
|
||||||
|
isList: false,
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../types/routing"
|
||||||
|
import { listPriceLists } from "./utils"
|
||||||
|
|
||||||
|
export const GET = async (
|
||||||
|
req: AuthenticatedMedusaRequest,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const { limit, offset } = req.validatedQuery
|
||||||
|
const [priceLists, count] = await listPriceLists({
|
||||||
|
container: req.scope,
|
||||||
|
fields: req.listConfig.select!,
|
||||||
|
variables: {
|
||||||
|
filters: req.filterableFields,
|
||||||
|
order: req.listConfig.order,
|
||||||
|
skip: req.listConfig.skip,
|
||||||
|
take: req.listConfig.take,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
count,
|
||||||
|
price_lists: priceLists,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./list-price-lists"
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { LinkModuleUtils, ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { MedusaContainer, PriceListDTO } from "@medusajs/types"
|
||||||
|
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||||
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
import { PriceListRelations, priceListRemoteQueryFields } from "../query-config"
|
||||||
|
|
||||||
|
enum RuleAttributes {
|
||||||
|
CUSTOMER_GROUP_ID = "customer_group_id",
|
||||||
|
REGION_ID = "region_id",
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listPriceLists({
|
||||||
|
container,
|
||||||
|
fields,
|
||||||
|
variables,
|
||||||
|
}: {
|
||||||
|
container: MedusaContainer
|
||||||
|
fields: string[]
|
||||||
|
variables: Record<string, any>
|
||||||
|
}): Promise<[PriceListDTO[], number]> {
|
||||||
|
const remoteQuery = container.resolve(LinkModuleUtils.REMOTE_QUERY)
|
||||||
|
const customerModule = container.resolve(ModuleRegistrationName.CUSTOMER)
|
||||||
|
|
||||||
|
const remoteQueryFields = fields.filter(
|
||||||
|
(field) =>
|
||||||
|
!field.startsWith(PriceListRelations.CUSTOMER_GROUPS) &&
|
||||||
|
!field.startsWith(PriceListRelations.PRICES)
|
||||||
|
)
|
||||||
|
const customerGroupFields = fields.filter((field) =>
|
||||||
|
field.startsWith(PriceListRelations.CUSTOMER_GROUPS)
|
||||||
|
)
|
||||||
|
const pricesFields = fields.filter((field) =>
|
||||||
|
field.startsWith(PriceListRelations.PRICES)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (customerGroupFields.length) {
|
||||||
|
remoteQueryFields.push(...priceListRemoteQueryFields.customerGroupsFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pricesFields.length) {
|
||||||
|
remoteQueryFields.push(...priceListRemoteQueryFields.pricesFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryObject = remoteQueryObjectFromString({
|
||||||
|
entryPoint: "price_list",
|
||||||
|
fields: remoteQueryFields,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
rows: priceLists,
|
||||||
|
metadata: { count },
|
||||||
|
} = await remoteQuery(queryObject)
|
||||||
|
|
||||||
|
if (!count) {
|
||||||
|
return [[], 0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerGroupIds: string[] = customerGroupFields.length
|
||||||
|
? priceLists
|
||||||
|
.map((priceList) => priceList.price_list_rules)
|
||||||
|
.flat(1)
|
||||||
|
.filter(
|
||||||
|
(rule) =>
|
||||||
|
rule.rule_type?.rule_attribute === RuleAttributes.CUSTOMER_GROUP_ID
|
||||||
|
)
|
||||||
|
.map((rule) => rule.price_list_rule_values.map((plrv) => plrv.value))
|
||||||
|
.flat(1)
|
||||||
|
: []
|
||||||
|
|
||||||
|
const customerGroups = await customerModule.listCustomerGroups(
|
||||||
|
{ id: customerGroupIds },
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
const customerGroupIdMap = new Map(customerGroups.map((cg) => [cg.id, cg]))
|
||||||
|
|
||||||
|
for (const priceList of priceLists) {
|
||||||
|
const priceSetMoneyAmounts = priceList.price_set_money_amounts || []
|
||||||
|
const priceListRulesData = priceList.price_list_rules || []
|
||||||
|
delete priceList.price_set_money_amounts
|
||||||
|
delete priceList.price_list_rules
|
||||||
|
|
||||||
|
if (pricesFields.length) {
|
||||||
|
priceList.prices = priceSetMoneyAmounts.map((priceSetMoneyAmount) => {
|
||||||
|
const productVariant = priceSetMoneyAmount.price_set.variant
|
||||||
|
const rules = priceSetMoneyAmount.price_rules.reduce((acc, curr) => {
|
||||||
|
acc[curr.rule_type.rule_attribute] = curr.value
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...priceSetMoneyAmount.money_amount,
|
||||||
|
price_list_id: priceList.id,
|
||||||
|
variant_id: productVariant?.id ?? null,
|
||||||
|
region_id: rules["region_id"] ?? null,
|
||||||
|
rules,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
priceList.name = priceList.title
|
||||||
|
delete priceList.title
|
||||||
|
|
||||||
|
if (customerGroupFields.length) {
|
||||||
|
const customerGroupPriceListRule = priceListRulesData.find(
|
||||||
|
(plr) =>
|
||||||
|
plr.rule_type.rule_attribute === RuleAttributes.CUSTOMER_GROUP_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
priceList.customer_groups =
|
||||||
|
customerGroupPriceListRule?.price_list_rule_values
|
||||||
|
.map((cgr) => customerGroupIdMap.get(cgr.value))
|
||||||
|
.filter(Boolean) || []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizedPriceLists = priceLists.map((priceList) => {
|
||||||
|
return cleanResponseData(priceList, fields)
|
||||||
|
})
|
||||||
|
|
||||||
|
return [sanitizedPriceLists, count]
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { FindParams } from "../../../types/common"
|
||||||
|
|
||||||
|
export class AdminGetPriceListsParams extends FindParams {}
|
||||||
|
export class AdminGetPriceListsPriceListParams extends FindParams {}
|
||||||
@@ -5,6 +5,7 @@ import { adminCurrencyRoutesMiddlewares } from "./admin/currencies/middlewares"
|
|||||||
import { adminCustomerGroupRoutesMiddlewares } from "./admin/customer-groups/middlewares"
|
import { adminCustomerGroupRoutesMiddlewares } from "./admin/customer-groups/middlewares"
|
||||||
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
||||||
import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
||||||
|
import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares"
|
||||||
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
||||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||||
@@ -43,5 +44,6 @@ export const config: MiddlewaresConfig = {
|
|||||||
...adminCurrencyRoutesMiddlewares,
|
...adminCurrencyRoutesMiddlewares,
|
||||||
...storeCurrencyRoutesMiddlewares,
|
...storeCurrencyRoutesMiddlewares,
|
||||||
...adminProductRoutesMiddlewares,
|
...adminProductRoutesMiddlewares,
|
||||||
|
...adminPriceListsRoutesMiddlewares,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { NextFunction, Request, Response } from "express"
|
import { buildSelects, objectToStringPath } from "@medusajs/utils"
|
||||||
import { ClassConstructor } from "../../types/global"
|
|
||||||
import { validator } from "../../utils/validator"
|
|
||||||
import { ValidatorOptions } from "class-validator"
|
import { ValidatorOptions } from "class-validator"
|
||||||
import { default as normalizeQuery } from "./normalized-query"
|
import { NextFunction, Request, Response } from "express"
|
||||||
|
import { omit } from "lodash"
|
||||||
|
import { BaseEntity } from "../../interfaces"
|
||||||
|
import { FindConfig, QueryConfig, RequestQueryFields } from "../../types/common"
|
||||||
|
import { ClassConstructor } from "../../types/global"
|
||||||
|
import { removeUndefinedProperties } from "../../utils"
|
||||||
import {
|
import {
|
||||||
prepareListQuery,
|
prepareListQuery,
|
||||||
prepareRetrieveQuery,
|
prepareRetrieveQuery,
|
||||||
} from "../../utils/get-query-config"
|
} from "../../utils/get-query-config"
|
||||||
import { BaseEntity } from "../../interfaces"
|
import { validator } from "../../utils/validator"
|
||||||
import { FindConfig, QueryConfig, RequestQueryFields } from "../../types/common"
|
import { default as normalizeQuery } from "./normalized-query"
|
||||||
import { omit } from "lodash"
|
|
||||||
import { removeUndefinedProperties } from "../../utils"
|
|
||||||
import { buildSelects, objectToStringPath } from "@medusajs/utils"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware that transform the query input for the admin end points
|
* Middleware that transform the query input for the admin end points
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { MedusaContainer } from "@medusajs/types"
|
|
||||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
|
||||||
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
||||||
import { PriceList } from "../../../.."
|
import { PriceList } from "../../../.."
|
||||||
import PriceListService from "../../../../services/price-list"
|
import PriceListService from "../../../../services/price-list"
|
||||||
import { getPriceListPricingModule } from "./modules-queries"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [get] /admin/price-lists/{id}
|
* @oas [get] /admin/price-lists/{id}
|
||||||
@@ -87,22 +84,13 @@ import { getPriceListPricingModule } from "./modules-queries"
|
|||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
|
||||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
|
||||||
const priceListService: PriceListService =
|
const priceListService: PriceListService =
|
||||||
req.scope.resolve("priceListService")
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
let priceList
|
const priceList = await priceListService.retrieve(id, {
|
||||||
|
|
||||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
|
||||||
priceList = await getPriceListPricingModule(id, {
|
|
||||||
container: req.scope as MedusaContainer,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
priceList = await priceListService.retrieve(id, {
|
|
||||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||||
relations: defaultAdminPriceListRelations,
|
relations: defaultAdminPriceListRelations,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json({ price_list: priceList })
|
res.status(200).json({ price_list: priceList })
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
|
||||||
import { Type } from "class-transformer"
|
import { Type } from "class-transformer"
|
||||||
import { IsNumber, IsOptional, IsString } from "class-validator"
|
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||||
import { Request } from "express"
|
import { Request } from "express"
|
||||||
import PriceListService from "../../../../services/price-list"
|
import PriceListService from "../../../../services/price-list"
|
||||||
import { FilterablePriceListProps } from "../../../../types/price-list"
|
import { FilterablePriceListProps } from "../../../../types/price-list"
|
||||||
import { MedusaContainer } from "@medusajs/types"
|
|
||||||
import { listAndCountPriceListPricingModule } from "./modules-queries"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [get] /admin/price-lists
|
* @oas [get] /admin/price-lists
|
||||||
@@ -190,27 +187,14 @@ import { listAndCountPriceListPricingModule } from "./modules-queries"
|
|||||||
* $ref: "#/components/responses/500_error"
|
* $ref: "#/components/responses/500_error"
|
||||||
*/
|
*/
|
||||||
export default async (req: Request, res) => {
|
export default async (req: Request, res) => {
|
||||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
|
||||||
|
|
||||||
const validated = req.validatedQuery
|
const validated = req.validatedQuery
|
||||||
let priceLists
|
|
||||||
let count
|
|
||||||
|
|
||||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
|
||||||
[priceLists, count] = await listAndCountPriceListPricingModule({
|
|
||||||
filters: req.filterableFields,
|
|
||||||
listConfig: req.listConfig,
|
|
||||||
container: req.scope as MedusaContainer,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const priceListService: PriceListService =
|
const priceListService: PriceListService =
|
||||||
req.scope.resolve("priceListService")
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
;[priceLists, count] = await priceListService.listAndCount(
|
const [priceLists, count] = await priceListService.listAndCount(
|
||||||
req.filterableFields,
|
req.filterableFields,
|
||||||
req.listConfig
|
req.listConfig
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
price_lists: priceLists,
|
price_lists: priceLists,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MedusaContainer } from "@medusajs/types"
|
import { MedusaContainer } from "@medusajs/types"
|
||||||
import { PriceList } from "../../../../../models"
|
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
import { PriceList } from "../../../../../models"
|
||||||
import { listAndCountPriceListPricingModule } from "./list-and-count-price-lists"
|
import { listAndCountPriceListPricingModule } from "./list-and-count-price-lists"
|
||||||
|
|
||||||
export async function getPriceListPricingModule(
|
export async function getPriceListPricingModule(
|
||||||
@@ -12,9 +12,7 @@ export async function getPriceListPricingModule(
|
|||||||
}
|
}
|
||||||
): Promise<PriceList> {
|
): Promise<PriceList> {
|
||||||
const [priceLists, count] = await listAndCountPriceListPricingModule({
|
const [priceLists, count] = await listAndCountPriceListPricingModule({
|
||||||
filters: {
|
filters: { id: [id] },
|
||||||
id: [id],
|
|
||||||
},
|
|
||||||
container,
|
container,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+10
-34
@@ -1,7 +1,7 @@
|
|||||||
|
import { LinkModuleUtils, ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { FilterablePriceListProps, MedusaContainer } from "@medusajs/types"
|
import { FilterablePriceListProps, MedusaContainer } from "@medusajs/types"
|
||||||
import { FindConfig } from "../../../../../types/common"
|
|
||||||
import { CustomerGroup, MoneyAmount, PriceList } from "../../../../../models"
|
import { CustomerGroup, MoneyAmount, PriceList } from "../../../../../models"
|
||||||
import { CustomerGroupService } from "../../../../../services"
|
import { FindConfig } from "../../../../../types/common"
|
||||||
import { defaultAdminPriceListRemoteQueryObject } from "../index"
|
import { defaultAdminPriceListRemoteQueryObject } from "../index"
|
||||||
|
|
||||||
export async function listAndCountPriceListPricingModule({
|
export async function listAndCountPriceListPricingModule({
|
||||||
@@ -13,10 +13,8 @@ export async function listAndCountPriceListPricingModule({
|
|||||||
filters?: FilterablePriceListProps
|
filters?: FilterablePriceListProps
|
||||||
listConfig?: FindConfig<PriceList>
|
listConfig?: FindConfig<PriceList>
|
||||||
}): Promise<[PriceList[], number]> {
|
}): Promise<[PriceList[], number]> {
|
||||||
const remoteQuery = container.resolve("remoteQuery")
|
const remoteQuery = container.resolve(LinkModuleUtils.REMOTE_QUERY)
|
||||||
const customerGroupService: CustomerGroupService = container.resolve(
|
const customerModule = container.resolve(ModuleRegistrationName.CUSTOMER)
|
||||||
"customerGroupService"
|
|
||||||
)
|
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
price_list: {
|
price_list: {
|
||||||
@@ -44,17 +42,8 @@ export async function listAndCountPriceListPricingModule({
|
|||||||
)
|
)
|
||||||
.flat(2)
|
.flat(2)
|
||||||
|
|
||||||
const priceListCustomerGroups = await customerGroupService.list(
|
const customerGroups = await customerModule.list({ id: customerGroupIds }, {})
|
||||||
{ id: customerGroupIds },
|
const customerGroupIdMap = new Map(customerGroups.map((cg) => [cg.id, cg]))
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
const customerGroupIdCustomerGroupMap = new Map(
|
|
||||||
priceListCustomerGroups.map((customerGroup) => [
|
|
||||||
customerGroup.id,
|
|
||||||
customerGroup,
|
|
||||||
])
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const priceList of priceLists) {
|
for (const priceList of priceLists) {
|
||||||
const priceSetMoneyAmounts = priceList.price_set_money_amounts || []
|
const priceSetMoneyAmounts = priceList.price_set_money_amounts || []
|
||||||
@@ -83,27 +72,14 @@ export async function listAndCountPriceListPricingModule({
|
|||||||
priceList.name = priceList.title
|
priceList.name = priceList.title
|
||||||
delete priceList.title
|
delete priceList.title
|
||||||
|
|
||||||
const customerGroupPriceListRule = priceListRulesData.find(
|
const customerGroupRule = priceListRulesData.find(
|
||||||
(plr) => plr.rule_type.rule_attribute === "customer_group_id"
|
(plr) => plr.rule_type.rule_attribute === "customer_group_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
|
||||||
customerGroupPriceListRule &&
|
|
||||||
customerGroupPriceListRule?.price_list_rule_values
|
|
||||||
) {
|
|
||||||
priceList.customer_groups =
|
priceList.customer_groups =
|
||||||
customerGroupPriceListRule?.price_list_rule_values
|
customerGroupRule?.price_list_rule_values
|
||||||
.map((customerGroupRule) =>
|
.map((cgr) => customerGroupIdMap.get(cgr.value))
|
||||||
customerGroupIdCustomerGroupMap.get(customerGroupRule.value)
|
.filter((cg): cg is CustomerGroup => !!cg) || []
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
(
|
|
||||||
customerGroup: CustomerGroup | undefined
|
|
||||||
): customerGroup is CustomerGroup => !!customerGroup
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
priceList.customer_groups = []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [priceLists, count]
|
return [priceLists, count]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { pick } from "lodash"
|
import { pickDeep } from "@medusajs/utils"
|
||||||
import { omitDeep } from "./omit-deep"
|
import { omitDeep } from "./omit-deep"
|
||||||
|
|
||||||
// TODO: once the legacy totals decoration will be removed.
|
// TODO: once the legacy totals decoration will be removed.
|
||||||
@@ -52,9 +52,9 @@ function cleanResponseData<T extends unknown | unknown[]>(
|
|||||||
|
|
||||||
fields = [...fieldsSet]
|
fields = [...fieldsSet]
|
||||||
|
|
||||||
arrayData = arrayData.map((record) =>
|
arrayData = arrayData.map((record) => {
|
||||||
pick(omitDeep(record, EXCLUDED_FIELDS), fields)
|
return pickDeep(omitDeep(record, EXCLUDED_FIELDS), fields)
|
||||||
)
|
})
|
||||||
|
|
||||||
return (isDataArray ? arrayData : arrayData[0]) as T extends []
|
return (isDataArray ? arrayData : arrayData[0]) as T extends []
|
||||||
? Partial<T>[]
|
? Partial<T>[]
|
||||||
|
|||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
import { MikroOrmWrapper } from "../../../utils"
|
import { MikroOrmWrapper } from "../../../utils"
|
||||||
|
|
||||||
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
import { IPricingModuleService } from "@medusajs/types"
|
import { IPricingModuleService } from "@medusajs/types"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
import { initModules } from "medusa-test-utils"
|
||||||
import { createPriceLists } from "../../../__fixtures__/price-list"
|
import { createPriceLists } from "../../../__fixtures__/price-list"
|
||||||
import { createPriceSets } from "../../../__fixtures__/price-set"
|
import { createPriceSets } from "../../../__fixtures__/price-set"
|
||||||
import { Modules } from "@medusajs/modules-sdk"
|
|
||||||
import { initModules } from "medusa-test-utils"
|
|
||||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ export * from "./medusa-container"
|
|||||||
export * from "./object-from-string-path"
|
export * from "./object-from-string-path"
|
||||||
export * from "./object-to-string-path"
|
export * from "./object-to-string-path"
|
||||||
export * from "./optional-numeric-serializer"
|
export * from "./optional-numeric-serializer"
|
||||||
|
export * from "./pick-deep"
|
||||||
export * from "./pick-value-from-object"
|
export * from "./pick-value-from-object"
|
||||||
export * from "./plurailze"
|
export * from "./plurailze"
|
||||||
|
export * from "./prefix-array-items"
|
||||||
export * from "./promise-all"
|
export * from "./promise-all"
|
||||||
export * from "./remote-query-object-from-string"
|
export * from "./remote-query-object-from-string"
|
||||||
export * from "./remote-query-object-to-string"
|
export * from "./remote-query-object-to-string"
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { isObject } from "./is-object"
|
||||||
|
|
||||||
|
export function pickDeep<T extends object = object>(
|
||||||
|
input: object,
|
||||||
|
fields: Array<number | string>,
|
||||||
|
prefix: string = ""
|
||||||
|
): T {
|
||||||
|
if (!input) {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(input).reduce((nextInput, [key, value]) => {
|
||||||
|
const fieldKey = withPrefix(key, prefix)
|
||||||
|
const fieldMatches = fields.includes(fieldKey)
|
||||||
|
const partialKeyMatch =
|
||||||
|
fields.filter((field) => field.toString().startsWith(`${fieldKey}.`))
|
||||||
|
.length > 0
|
||||||
|
|
||||||
|
const valueIsObject = isObject(value)
|
||||||
|
const valueIsArray = Array.isArray(value)
|
||||||
|
|
||||||
|
if (fieldMatches && (valueIsObject || valueIsArray)) {
|
||||||
|
nextInput[key] = value
|
||||||
|
|
||||||
|
return nextInput
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fieldMatches && !partialKeyMatch) {
|
||||||
|
return nextInput
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueIsArray) {
|
||||||
|
nextInput[key] = value.map((arrItem) => {
|
||||||
|
if (isObject(arrItem)) {
|
||||||
|
return pickDeep(arrItem, fields, withPrefix(key, prefix))
|
||||||
|
}
|
||||||
|
return arrItem
|
||||||
|
})
|
||||||
|
|
||||||
|
return nextInput
|
||||||
|
} else if (valueIsObject) {
|
||||||
|
if (Object.keys(value).length) {
|
||||||
|
nextInput[key] = pickDeep(value, fields, withPrefix(key, prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextInput
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMatches) {
|
||||||
|
nextInput[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextInput
|
||||||
|
}, {} as T)
|
||||||
|
}
|
||||||
|
|
||||||
|
function withPrefix(key: string, prefix: string): string {
|
||||||
|
if (prefix.length) {
|
||||||
|
return `${prefix}.${key}`
|
||||||
|
} else {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Prefixes an array of strings with a specified string
|
||||||
|
* @param array
|
||||||
|
* @param prefix
|
||||||
|
*/
|
||||||
|
export function prefixArrayItems(array: string[], prefix: string): string[] {
|
||||||
|
return array.map((arrEl) => `${prefix}${arrEl}`)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user