feat(medusa): enable price list prices for product endpoints (#10197)
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ApiKeyType, Modules, ProductStatus } from "@medusajs/utils"
|
||||
import {
|
||||
ApiKeyType,
|
||||
Modules,
|
||||
PriceListStatus,
|
||||
PriceListType,
|
||||
ProductStatus,
|
||||
} from "@medusajs/utils"
|
||||
import qs from "qs"
|
||||
import {
|
||||
adminHeaders,
|
||||
@@ -9,6 +15,7 @@ import {
|
||||
generateStoreHeaders,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
import { getProductFixture } from "../../../../helpers/fixtures"
|
||||
import { createAuthenticatedCustomer } from "../../../../modules/helpers/create-authenticated-customer"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -27,10 +34,13 @@ medusaIntegrationTestRunner({
|
||||
let variant2
|
||||
let variant3
|
||||
let variant4
|
||||
let region
|
||||
let inventoryItem1
|
||||
let inventoryItem2
|
||||
let storeHeaders
|
||||
let publishableKey
|
||||
let storeHeadersWithCustomer
|
||||
let customer
|
||||
|
||||
const createProducts = async (data) => {
|
||||
const response = await api.post(
|
||||
@@ -86,6 +96,19 @@ medusaIntegrationTestRunner({
|
||||
publishableKey = await generatePublishableKey(appContainer)
|
||||
storeHeaders = generateStoreHeaders({ publishableKey })
|
||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||
const result = await createAuthenticatedCustomer(api, storeHeaders, {
|
||||
first_name: "tony",
|
||||
last_name: "stark",
|
||||
email: "tony@stark-industries.com",
|
||||
})
|
||||
|
||||
customer = result.customer
|
||||
storeHeadersWithCustomer = {
|
||||
headers: {
|
||||
...storeHeaders.headers,
|
||||
authorization: `Bearer ${result.jwt}`,
|
||||
},
|
||||
}
|
||||
|
||||
const storeModule: IStoreModuleService = appContainer.resolve(
|
||||
Modules.STORE
|
||||
@@ -104,6 +127,14 @@ medusaIntegrationTestRunner({
|
||||
{ currency_code: "dkk" },
|
||||
],
|
||||
})
|
||||
|
||||
region = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{ name: "Test Region", currency_code: "usd" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
})
|
||||
|
||||
describe("Get products based on publishable key", () => {
|
||||
@@ -954,14 +985,6 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should list products with prices when context is present", async () => {
|
||||
const region = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{ name: "Test Region", currency_code: "usd" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
let response = await api.get(
|
||||
`/store/products?fields=*variants.calculated_price®ion_id=${region.id}`,
|
||||
storeHeaders
|
||||
@@ -1023,6 +1046,192 @@ medusaIntegrationTestRunner({
|
||||
expect(response.data.products).toEqual(expectation)
|
||||
})
|
||||
|
||||
describe("with price lists", () => {
|
||||
let customerGroup
|
||||
|
||||
beforeEach(async () => {
|
||||
customerGroup = (
|
||||
await api.post(
|
||||
"/admin/customer-groups",
|
||||
{ name: "VIP" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.customer_group
|
||||
|
||||
await api.post(
|
||||
`/admin/customer-groups/${customerGroup.id}/customers`,
|
||||
{ add: [customer.id] },
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("should list products with prices with a sale price list price", async () => {
|
||||
const priceList = (
|
||||
await api.post(
|
||||
`/admin/price-lists`,
|
||||
{
|
||||
title: "test price list",
|
||||
description: "test",
|
||||
status: PriceListStatus.ACTIVE,
|
||||
type: PriceListType.SALE,
|
||||
prices: [
|
||||
{
|
||||
amount: 350,
|
||||
currency_code: "usd",
|
||||
variant_id: product.variants[0].id,
|
||||
},
|
||||
],
|
||||
rules: { customer_group_id: [customerGroup.id] },
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.price_list
|
||||
|
||||
let response = await api.get(
|
||||
`/store/products?fields=*variants.calculated_price®ion_id=${region.id}`,
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
|
||||
const expectation = expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product.id,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
calculated_price: {
|
||||
id: expect.any(String),
|
||||
is_calculated_price_price_list: true,
|
||||
is_calculated_price_tax_inclusive: false,
|
||||
calculated_amount: 350,
|
||||
raw_calculated_amount: {
|
||||
value: "350",
|
||||
precision: 20,
|
||||
},
|
||||
is_original_price_price_list: false,
|
||||
is_original_price_tax_inclusive: false,
|
||||
original_amount: 3000,
|
||||
raw_original_amount: {
|
||||
value: "3000",
|
||||
precision: 20,
|
||||
},
|
||||
currency_code: "usd",
|
||||
calculated_price: {
|
||||
id: expect.any(String),
|
||||
price_list_id: priceList.id,
|
||||
price_list_type: "sale",
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
original_price: {
|
||||
id: expect.any(String),
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(3)
|
||||
expect(response.data.products).toEqual(expectation)
|
||||
|
||||
// with only region_id
|
||||
response = await api.get(
|
||||
`/store/products?region_id=${region.id}`,
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.products).toEqual(expectation)
|
||||
})
|
||||
|
||||
it("should list products with prices with a override price list price", async () => {
|
||||
const priceList = (
|
||||
await api.post(
|
||||
`/admin/price-lists`,
|
||||
{
|
||||
title: "test price list",
|
||||
description: "test",
|
||||
status: PriceListStatus.ACTIVE,
|
||||
type: PriceListType.OVERRIDE,
|
||||
prices: [
|
||||
{
|
||||
amount: 350,
|
||||
currency_code: "usd",
|
||||
variant_id: product.variants[0].id,
|
||||
},
|
||||
],
|
||||
rules: { customer_group_id: [customerGroup.id] },
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.price_list
|
||||
|
||||
let response = await api.get(
|
||||
`/store/products?fields=*variants.calculated_price®ion_id=${region.id}`,
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
|
||||
const expectation = expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product.id,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
calculated_price: {
|
||||
id: expect.any(String),
|
||||
is_calculated_price_price_list: true,
|
||||
is_calculated_price_tax_inclusive: false,
|
||||
calculated_amount: 350,
|
||||
raw_calculated_amount: {
|
||||
value: "350",
|
||||
precision: 20,
|
||||
},
|
||||
is_original_price_price_list: true,
|
||||
is_original_price_tax_inclusive: false,
|
||||
original_amount: 350,
|
||||
raw_original_amount: {
|
||||
value: "350",
|
||||
precision: 20,
|
||||
},
|
||||
currency_code: "usd",
|
||||
calculated_price: {
|
||||
id: expect.any(String),
|
||||
price_list_id: priceList.id,
|
||||
price_list_type: "override",
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
original_price: {
|
||||
id: expect.any(String),
|
||||
price_list_id: priceList.id,
|
||||
price_list_type: "override",
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(3)
|
||||
expect(response.data.products).toEqual(expectation)
|
||||
|
||||
// with only region_id
|
||||
response = await api.get(
|
||||
`/store/products?region_id=${region.id}`,
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.products).toEqual(expectation)
|
||||
})
|
||||
})
|
||||
|
||||
describe("with inventory items", () => {
|
||||
let location1
|
||||
let location2
|
||||
@@ -1291,14 +1500,6 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should get product with prices when context is present", async () => {
|
||||
const region = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{ name: "Test Region", currency_code: "usd" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
let response = await api.get(
|
||||
`/store/products/${product.id}?fields=*variants.calculated_price®ion_id=${region.id}`,
|
||||
storeHeaders
|
||||
@@ -1356,6 +1557,186 @@ medusaIntegrationTestRunner({
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.product).toEqual(expectation)
|
||||
})
|
||||
|
||||
describe("with price lists", () => {
|
||||
let customerGroup
|
||||
|
||||
beforeEach(async () => {
|
||||
customerGroup = (
|
||||
await api.post(
|
||||
"/admin/customer-groups",
|
||||
{ name: "VIP" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.customer_group
|
||||
|
||||
await api.post(
|
||||
`/admin/customer-groups/${customerGroup.id}/customers`,
|
||||
{ add: [customer.id] },
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("should return product with sale price list prices", async () => {
|
||||
const priceList = (
|
||||
await api.post(
|
||||
`/admin/price-lists`,
|
||||
{
|
||||
title: "test price list",
|
||||
description: "test",
|
||||
status: PriceListStatus.ACTIVE,
|
||||
type: PriceListType.SALE,
|
||||
prices: [
|
||||
{
|
||||
amount: 350,
|
||||
currency_code: "usd",
|
||||
variant_id: product.variants[0].id,
|
||||
},
|
||||
],
|
||||
rules: { customer_group_id: [customerGroup.id] },
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.price_list
|
||||
|
||||
let response = await api.get(
|
||||
`/store/products/${product.id}?fields=*variants.calculated_price®ion_id=${region.id}`,
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
|
||||
const expectation = expect.objectContaining({
|
||||
id: product.id,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
calculated_price: {
|
||||
id: expect.any(String),
|
||||
is_calculated_price_price_list: true,
|
||||
is_calculated_price_tax_inclusive: false,
|
||||
calculated_amount: 350,
|
||||
raw_calculated_amount: {
|
||||
value: "350",
|
||||
precision: 20,
|
||||
},
|
||||
is_original_price_price_list: false,
|
||||
is_original_price_tax_inclusive: false,
|
||||
original_amount: 3000,
|
||||
raw_original_amount: {
|
||||
value: "3000",
|
||||
precision: 20,
|
||||
},
|
||||
currency_code: "usd",
|
||||
calculated_price: {
|
||||
id: expect.any(String),
|
||||
price_list_id: priceList.id,
|
||||
price_list_type: "sale",
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
original_price: {
|
||||
id: expect.any(String),
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.product).toEqual(expectation)
|
||||
|
||||
// with only region_id
|
||||
response = await api.get(
|
||||
`/store/products/${product.id}?region_id=${region.id}`,
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.product).toEqual(expectation)
|
||||
})
|
||||
|
||||
it("should list products with prices with a override price list price", async () => {
|
||||
const priceList = (
|
||||
await api.post(
|
||||
`/admin/price-lists`,
|
||||
{
|
||||
title: "test price list",
|
||||
description: "test",
|
||||
status: PriceListStatus.ACTIVE,
|
||||
type: PriceListType.OVERRIDE,
|
||||
prices: [
|
||||
{
|
||||
amount: 350,
|
||||
currency_code: "usd",
|
||||
variant_id: product.variants[0].id,
|
||||
},
|
||||
],
|
||||
rules: { customer_group_id: [customerGroup.id] },
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.price_list
|
||||
|
||||
let response = await api.get(
|
||||
`/store/products/${product.id}?fields=*variants.calculated_price®ion_id=${region.id}`,
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
|
||||
const expectation = expect.objectContaining({
|
||||
id: product.id,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
calculated_price: {
|
||||
id: expect.any(String),
|
||||
is_calculated_price_price_list: true,
|
||||
is_calculated_price_tax_inclusive: false,
|
||||
calculated_amount: 350,
|
||||
raw_calculated_amount: {
|
||||
value: "350",
|
||||
precision: 20,
|
||||
},
|
||||
is_original_price_price_list: true,
|
||||
is_original_price_tax_inclusive: false,
|
||||
original_amount: 350,
|
||||
raw_original_amount: {
|
||||
value: "350",
|
||||
precision: 20,
|
||||
},
|
||||
currency_code: "usd",
|
||||
calculated_price: {
|
||||
id: expect.any(String),
|
||||
price_list_id: priceList.id,
|
||||
price_list_type: "override",
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
original_price: {
|
||||
id: expect.any(String),
|
||||
price_list_id: priceList.id,
|
||||
price_list_type: "override",
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.product).toEqual(expectation)
|
||||
|
||||
// with only region_id
|
||||
response = await api.get(
|
||||
`/store/products/${product.id}?region_id=${region.id}`,
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.product).toEqual(expectation)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Tax handling", () => {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { isPresent, ProductStatus } from "@medusajs/framework/utils"
|
||||
import { validateAndTransformQuery } from "@medusajs/framework"
|
||||
import {
|
||||
applyDefaultFilters,
|
||||
applyParamsAsFilters,
|
||||
authenticate,
|
||||
clearFiltersByKey,
|
||||
maybeApplyLinkFilter,
|
||||
MiddlewareRoute,
|
||||
setContext,
|
||||
} from "@medusajs/framework/http"
|
||||
import { isPresent, ProductStatus } from "@medusajs/framework/utils"
|
||||
import {
|
||||
filterByValidSalesChannels,
|
||||
normalizeDataForContext,
|
||||
setPricingContext,
|
||||
setTaxContext,
|
||||
} from "../../utils/middlewares"
|
||||
import { validateAndTransformQuery } from "@medusajs/framework"
|
||||
import { maybeApplyStockLocationId } from "./helpers"
|
||||
import * as QueryConfig from "./query-config"
|
||||
import { StoreGetProductsParams } from "./validators"
|
||||
@@ -23,6 +24,9 @@ export const storeProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
method: ["GET"],
|
||||
matcher: "/store/products",
|
||||
middlewares: [
|
||||
authenticate("customer", ["session", "bearer"], {
|
||||
allowUnauthenticated: true,
|
||||
}),
|
||||
validateAndTransformQuery(
|
||||
StoreGetProductsParams,
|
||||
QueryConfig.listProductQueryConfig
|
||||
@@ -60,6 +64,9 @@ export const storeProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
method: ["GET"],
|
||||
matcher: "/store/products/:id",
|
||||
middlewares: [
|
||||
authenticate("customer", ["session", "bearer"], {
|
||||
allowUnauthenticated: true,
|
||||
}),
|
||||
validateAndTransformQuery(
|
||||
StoreGetProductsParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { MedusaPricingContext } from "@medusajs/framework/types"
|
||||
import { MedusaError } from "@medusajs/framework/utils"
|
||||
import { NextFunction } from "express"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
refetchEntities,
|
||||
refetchEntity,
|
||||
} from "@medusajs/framework/http"
|
||||
import { MedusaPricingContext } from "@medusajs/framework/types"
|
||||
import { MedusaError } from "@medusajs/framework/utils"
|
||||
import { NextFunction } from "express"
|
||||
|
||||
export function setPricingContext() {
|
||||
return async (req: AuthenticatedMedusaRequest, _, next: NextFunction) => {
|
||||
@@ -41,10 +41,10 @@ export function setPricingContext() {
|
||||
}
|
||||
|
||||
// Find all the customer groups the customer is a part of and set
|
||||
if (req.user?.customer_id) {
|
||||
if (req.auth_context?.actor_id) {
|
||||
const customerGroups = await refetchEntities(
|
||||
"customer_group",
|
||||
{ customer_id: req.user?.customer_id },
|
||||
{ customers: { id: req.auth_context.actor_id } },
|
||||
req.scope,
|
||||
["id"]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user