feat(medusa): enable price list prices for product endpoints (#10197)

This commit is contained in:
Riqwan Thamir authored and GitHub committed 2024-11-21 14:08:42 +01:00
1 parent 35afa04a93
commit d91da7b418
3 files changed
+412 -24

No files matched your search

@@ -1,6 +1,12 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils" import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { IStoreModuleService } from "@medusajs/types" 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 qs from "qs"
import { import {
adminHeaders, adminHeaders,
@@ -9,6 +15,7 @@ import {
generateStoreHeaders, generateStoreHeaders,
} from "../../../../helpers/create-admin-user" } from "../../../../helpers/create-admin-user"
import { getProductFixture } from "../../../../helpers/fixtures" import { getProductFixture } from "../../../../helpers/fixtures"
import { createAuthenticatedCustomer } from "../../../../modules/helpers/create-authenticated-customer"
jest.setTimeout(30000) jest.setTimeout(30000)
@@ -27,10 +34,13 @@ medusaIntegrationTestRunner({
let variant2 let variant2
let variant3 let variant3
let variant4 let variant4
let region
let inventoryItem1 let inventoryItem1
let inventoryItem2 let inventoryItem2
let storeHeaders let storeHeaders
let publishableKey let publishableKey
let storeHeadersWithCustomer
let customer
const createProducts = async (data) => { const createProducts = async (data) => {
const response = await api.post( const response = await api.post(
@@ -86,6 +96,19 @@ medusaIntegrationTestRunner({
publishableKey = await generatePublishableKey(appContainer) publishableKey = await generatePublishableKey(appContainer)
storeHeaders = generateStoreHeaders({ publishableKey }) storeHeaders = generateStoreHeaders({ publishableKey })
await createAdminUser(dbConnection, adminHeaders, appContainer) 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( const storeModule: IStoreModuleService = appContainer.resolve(
Modules.STORE Modules.STORE
@@ -104,6 +127,14 @@ medusaIntegrationTestRunner({
{ currency_code: "dkk" }, { 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", () => { describe("Get products based on publishable key", () => {
@@ -954,14 +985,6 @@ medusaIntegrationTestRunner({
}) })
it("should list products with prices when context is present", async () => { 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( let response = await api.get(
`/store/products?fields=*variants.calculated_price&region_id=${region.id}`, `/store/products?fields=*variants.calculated_price&region_id=${region.id}`,
storeHeaders storeHeaders
@@ -1023,6 +1046,192 @@ medusaIntegrationTestRunner({
expect(response.data.products).toEqual(expectation) 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&region_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&region_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", () => { describe("with inventory items", () => {
let location1 let location1
let location2 let location2
@@ -1291,14 +1500,6 @@ medusaIntegrationTestRunner({
}) })
it("should get product with prices when context is present", async () => { 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( let response = await api.get(
`/store/products/${product.id}?fields=*variants.calculated_price&region_id=${region.id}`, `/store/products/${product.id}?fields=*variants.calculated_price&region_id=${region.id}`,
storeHeaders storeHeaders
@@ -1356,6 +1557,186 @@ medusaIntegrationTestRunner({
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.product).toEqual(expectation) 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&region_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&region_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", () => { describe("Tax handling", () => {
@@ -1,19 +1,20 @@
import { isPresent, ProductStatus } from "@medusajs/framework/utils" import { validateAndTransformQuery } from "@medusajs/framework"
import { import {
applyDefaultFilters, applyDefaultFilters,
applyParamsAsFilters, applyParamsAsFilters,
authenticate,
clearFiltersByKey, clearFiltersByKey,
maybeApplyLinkFilter, maybeApplyLinkFilter,
MiddlewareRoute, MiddlewareRoute,
setContext, setContext,
} from "@medusajs/framework/http" } from "@medusajs/framework/http"
import { isPresent, ProductStatus } from "@medusajs/framework/utils"
import { import {
filterByValidSalesChannels, filterByValidSalesChannels,
normalizeDataForContext, normalizeDataForContext,
setPricingContext, setPricingContext,
setTaxContext, setTaxContext,
} from "../../utils/middlewares" } from "../../utils/middlewares"
import { validateAndTransformQuery } from "@medusajs/framework"
import { maybeApplyStockLocationId } from "./helpers" import { maybeApplyStockLocationId } from "./helpers"
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { StoreGetProductsParams } from "./validators" import { StoreGetProductsParams } from "./validators"
@@ -23,6 +24,9 @@ export const storeProductRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/store/products", matcher: "/store/products",
middlewares: [ middlewares: [
authenticate("customer", ["session", "bearer"], {
allowUnauthenticated: true,
}),
validateAndTransformQuery( validateAndTransformQuery(
StoreGetProductsParams, StoreGetProductsParams,
QueryConfig.listProductQueryConfig QueryConfig.listProductQueryConfig
@@ -60,6 +64,9 @@ export const storeProductRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/store/products/:id", matcher: "/store/products/:id",
middlewares: [ middlewares: [
authenticate("customer", ["session", "bearer"], {
allowUnauthenticated: true,
}),
validateAndTransformQuery( validateAndTransformQuery(
StoreGetProductsParams, StoreGetProductsParams,
QueryConfig.retrieveProductQueryConfig QueryConfig.retrieveProductQueryConfig
@@ -1,11 +1,11 @@
import { MedusaPricingContext } from "@medusajs/framework/types"
import { MedusaError } from "@medusajs/framework/utils"
import { NextFunction } from "express"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
refetchEntities, refetchEntities,
refetchEntity, refetchEntity,
} from "@medusajs/framework/http" } from "@medusajs/framework/http"
import { MedusaPricingContext } from "@medusajs/framework/types"
import { MedusaError } from "@medusajs/framework/utils"
import { NextFunction } from "express"
export function setPricingContext() { export function setPricingContext() {
return async (req: AuthenticatedMedusaRequest, _, next: NextFunction) => { 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 // 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( const customerGroups = await refetchEntities(
"customer_group", "customer_group",
{ customer_id: req.user?.customer_id }, { customers: { id: req.auth_context.actor_id } },
req.scope, req.scope,
["id"] ["id"]
) )