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:
7
.changeset/silent-forks-cough.md
Normal file
7
.changeset/silent-forks-cough.md
Normal file
@@ -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,
|
||||
gift_card_total: 0,
|
||||
gift_card_tax_total: 0,
|
||||
items: [{ refundable: 7200 }],
|
||||
claims: [],
|
||||
swaps: [],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -290,27 +290,29 @@ describe("/store/carts", () => {
|
||||
"/store/orders/order_test?fields=status&expand=billing_address"
|
||||
)
|
||||
|
||||
expect(Object.keys(response.data.order)).toEqual([
|
||||
// fields
|
||||
"status",
|
||||
expect(Object.keys(response.data.order).sort()).toEqual(
|
||||
[
|
||||
// fields
|
||||
"status",
|
||||
|
||||
// selected relations
|
||||
"billing_address",
|
||||
// selected relations
|
||||
"billing_address",
|
||||
|
||||
// totals
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"total",
|
||||
"subtotal",
|
||||
"paid_total",
|
||||
"refundable_amount",
|
||||
"gift_card_total",
|
||||
"gift_card_tax_total",
|
||||
"item_tax_total",
|
||||
"shipping_tax_total",
|
||||
])
|
||||
// totals
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"total",
|
||||
"subtotal",
|
||||
"paid_total",
|
||||
"refundable_amount",
|
||||
"gift_card_total",
|
||||
"gift_card_tax_total",
|
||||
"item_tax_total",
|
||||
"shipping_tax_total",
|
||||
].sort()
|
||||
)
|
||||
})
|
||||
|
||||
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",
|
||||
alias: "variant_link",
|
||||
},
|
||||
fieldAlias: {
|
||||
variant: "variant_link.variant",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
31
packages/medusa/src/api-v2/admin/price-lists/[id]/route.ts
Normal file
31
packages/medusa/src/api-v2/admin/price-lists/[id]/route.ts
Normal file
@@ -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 })
|
||||
}
|
||||
30
packages/medusa/src/api-v2/admin/price-lists/middlewares.ts
Normal file
30
packages/medusa/src/api-v2/admin/price-lists/middlewares.ts
Normal file
@@ -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
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
65
packages/medusa/src/api-v2/admin/price-lists/query-config.ts
Normal file
65
packages/medusa/src/api-v2/admin/price-lists/query-config.ts
Normal file
@@ -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,
|
||||
}
|
||||
29
packages/medusa/src/api-v2/admin/price-lists/route.ts
Normal file
29
packages/medusa/src/api-v2/admin/price-lists/route.ts
Normal file
@@ -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 { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
||||
import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
||||
import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares"
|
||||
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||
@@ -43,5 +44,6 @@ export const config: MiddlewaresConfig = {
|
||||
...adminCurrencyRoutesMiddlewares,
|
||||
...storeCurrencyRoutesMiddlewares,
|
||||
...adminProductRoutesMiddlewares,
|
||||
...adminPriceListsRoutesMiddlewares,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { NextFunction, Request, Response } from "express"
|
||||
import { ClassConstructor } from "../../types/global"
|
||||
import { validator } from "../../utils/validator"
|
||||
import { buildSelects, objectToStringPath } from "@medusajs/utils"
|
||||
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 {
|
||||
prepareListQuery,
|
||||
prepareRetrieveQuery,
|
||||
} from "../../utils/get-query-config"
|
||||
import { BaseEntity } from "../../interfaces"
|
||||
import { FindConfig, QueryConfig, RequestQueryFields } from "../../types/common"
|
||||
import { omit } from "lodash"
|
||||
import { removeUndefinedProperties } from "../../utils"
|
||||
import { buildSelects, objectToStringPath } from "@medusajs/utils"
|
||||
import { validator } from "../../utils/validator"
|
||||
import { default as normalizeQuery } from "./normalized-query"
|
||||
|
||||
/**
|
||||
* 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 { PriceList } from "../../../.."
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { getPriceListPricingModule } from "./modules-queries"
|
||||
|
||||
/**
|
||||
* @oas [get] /admin/price-lists/{id}
|
||||
@@ -87,22 +84,13 @@ import { getPriceListPricingModule } from "./modules-queries"
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
let priceList
|
||||
|
||||
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)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
}
|
||||
const priceList = await priceListService.retrieve(id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ price_list: priceList })
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { Type } from "class-transformer"
|
||||
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||
import { Request } from "express"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { FilterablePriceListProps } from "../../../../types/price-list"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { listAndCountPriceListPricingModule } from "./modules-queries"
|
||||
|
||||
/**
|
||||
* @oas [get] /admin/price-lists
|
||||
@@ -190,27 +187,14 @@ import { listAndCountPriceListPricingModule } from "./modules-queries"
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req: Request, res) => {
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
|
||||
const validated = req.validatedQuery
|
||||
let priceLists
|
||||
let count
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
[priceLists, count] = await listAndCountPriceListPricingModule({
|
||||
filters: req.filterableFields,
|
||||
listConfig: req.listConfig,
|
||||
container: req.scope as MedusaContainer,
|
||||
})
|
||||
} else {
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
;[priceLists, count] = await priceListService.listAndCount(
|
||||
req.filterableFields,
|
||||
req.listConfig
|
||||
)
|
||||
}
|
||||
const [priceLists, count] = await priceListService.listAndCount(
|
||||
req.filterableFields,
|
||||
req.listConfig
|
||||
)
|
||||
|
||||
res.json({
|
||||
price_lists: priceLists,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { PriceList } from "../../../../../models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { PriceList } from "../../../../../models"
|
||||
import { listAndCountPriceListPricingModule } from "./list-and-count-price-lists"
|
||||
|
||||
export async function getPriceListPricingModule(
|
||||
@@ -12,9 +12,7 @@ export async function getPriceListPricingModule(
|
||||
}
|
||||
): Promise<PriceList> {
|
||||
const [priceLists, count] = await listAndCountPriceListPricingModule({
|
||||
filters: {
|
||||
id: [id],
|
||||
},
|
||||
filters: { id: [id] },
|
||||
container,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LinkModuleUtils, ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { FilterablePriceListProps, MedusaContainer } from "@medusajs/types"
|
||||
import { FindConfig } from "../../../../../types/common"
|
||||
import { CustomerGroup, MoneyAmount, PriceList } from "../../../../../models"
|
||||
import { CustomerGroupService } from "../../../../../services"
|
||||
import { FindConfig } from "../../../../../types/common"
|
||||
import { defaultAdminPriceListRemoteQueryObject } from "../index"
|
||||
|
||||
export async function listAndCountPriceListPricingModule({
|
||||
@@ -13,10 +13,8 @@ export async function listAndCountPriceListPricingModule({
|
||||
filters?: FilterablePriceListProps
|
||||
listConfig?: FindConfig<PriceList>
|
||||
}): Promise<[PriceList[], number]> {
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
const customerGroupService: CustomerGroupService = container.resolve(
|
||||
"customerGroupService"
|
||||
)
|
||||
const remoteQuery = container.resolve(LinkModuleUtils.REMOTE_QUERY)
|
||||
const customerModule = container.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const query = {
|
||||
price_list: {
|
||||
@@ -44,17 +42,8 @@ export async function listAndCountPriceListPricingModule({
|
||||
)
|
||||
.flat(2)
|
||||
|
||||
const priceListCustomerGroups = await customerGroupService.list(
|
||||
{ id: customerGroupIds },
|
||||
{}
|
||||
)
|
||||
|
||||
const customerGroupIdCustomerGroupMap = new Map(
|
||||
priceListCustomerGroups.map((customerGroup) => [
|
||||
customerGroup.id,
|
||||
customerGroup,
|
||||
])
|
||||
)
|
||||
const customerGroups = await customerModule.list({ id: customerGroupIds }, {})
|
||||
const customerGroupIdMap = new Map(customerGroups.map((cg) => [cg.id, cg]))
|
||||
|
||||
for (const priceList of priceLists) {
|
||||
const priceSetMoneyAmounts = priceList.price_set_money_amounts || []
|
||||
@@ -83,27 +72,14 @@ export async function listAndCountPriceListPricingModule({
|
||||
priceList.name = priceList.title
|
||||
delete priceList.title
|
||||
|
||||
const customerGroupPriceListRule = priceListRulesData.find(
|
||||
const customerGroupRule = priceListRulesData.find(
|
||||
(plr) => plr.rule_type.rule_attribute === "customer_group_id"
|
||||
)
|
||||
|
||||
if (
|
||||
customerGroupPriceListRule &&
|
||||
customerGroupPriceListRule?.price_list_rule_values
|
||||
) {
|
||||
priceList.customer_groups =
|
||||
customerGroupPriceListRule?.price_list_rule_values
|
||||
.map((customerGroupRule) =>
|
||||
customerGroupIdCustomerGroupMap.get(customerGroupRule.value)
|
||||
)
|
||||
.filter(
|
||||
(
|
||||
customerGroup: CustomerGroup | undefined
|
||||
): customerGroup is CustomerGroup => !!customerGroup
|
||||
)
|
||||
} else {
|
||||
priceList.customer_groups = []
|
||||
}
|
||||
priceList.customer_groups =
|
||||
customerGroupRule?.price_list_rule_values
|
||||
.map((cgr) => customerGroupIdMap.get(cgr.value))
|
||||
.filter((cg): cg is CustomerGroup => !!cg) || []
|
||||
}
|
||||
|
||||
return [priceLists, count]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { pick } from "lodash"
|
||||
import { pickDeep } from "@medusajs/utils"
|
||||
import { omitDeep } from "./omit-deep"
|
||||
|
||||
// TODO: once the legacy totals decoration will be removed.
|
||||
@@ -52,9 +52,9 @@ function cleanResponseData<T extends unknown | unknown[]>(
|
||||
|
||||
fields = [...fieldsSet]
|
||||
|
||||
arrayData = arrayData.map((record) =>
|
||||
pick(omitDeep(record, EXCLUDED_FIELDS), fields)
|
||||
)
|
||||
arrayData = arrayData.map((record) => {
|
||||
return pickDeep(omitDeep(record, EXCLUDED_FIELDS), fields)
|
||||
})
|
||||
|
||||
return (isDataArray ? arrayData : arrayData[0]) as T extends []
|
||||
? Partial<T>[]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { MikroOrmWrapper } from "../../../utils"
|
||||
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { createPriceLists } from "../../../__fixtures__/price-list"
|
||||
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"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -31,8 +31,10 @@ export * from "./medusa-container"
|
||||
export * from "./object-from-string-path"
|
||||
export * from "./object-to-string-path"
|
||||
export * from "./optional-numeric-serializer"
|
||||
export * from "./pick-deep"
|
||||
export * from "./pick-value-from-object"
|
||||
export * from "./plurailze"
|
||||
export * from "./prefix-array-items"
|
||||
export * from "./promise-all"
|
||||
export * from "./remote-query-object-from-string"
|
||||
export * from "./remote-query-object-to-string"
|
||||
|
||||
63
packages/utils/src/common/pick-deep.ts
Normal file
63
packages/utils/src/common/pick-deep.ts
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
8
packages/utils/src/common/prefix-array-items.ts
Normal file
8
packages/utils/src/common/prefix-array-items.ts
Normal file
@@ -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