chore(medusa): Improve store list products (#3252)
* chore(medusa): Improve list products by 46.5 percents * fix handler * todo's * Create tricky-terms-wash.md --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
co-authored by
Oliver Windall Juhl
parent
13c200ad2f
commit
46547f29c7
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(medusa): Improve store list products
|
||||||
@@ -475,10 +475,10 @@ describe("/store/products", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("Product Category filtering", () => {
|
describe("Product Category filtering", () => {
|
||||||
let categoryWithProduct,
|
let categoryWithProduct
|
||||||
categoryWithoutProduct,
|
let categoryWithoutProduct
|
||||||
nestedCategoryWithProduct,
|
let nestedCategoryWithProduct
|
||||||
nested2CategoryWithProduct
|
let nested2CategoryWithProduct
|
||||||
const nestedCategoryWithProductId = "nested-category-with-product-id"
|
const nestedCategoryWithProductId = "nested-category-with-product-id"
|
||||||
const nested2CategoryWithProductId = "nested2-category-with-product-id"
|
const nested2CategoryWithProductId = "nested2-category-with-product-id"
|
||||||
const categoryWithProductId = "category-with-product-id"
|
const categoryWithProductId = "category-with-product-id"
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
CartService,
|
CartService,
|
||||||
ProductService,
|
ProductService,
|
||||||
ProductVariantInventoryService,
|
ProductVariantInventoryService,
|
||||||
RegionService,
|
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
import PricingService from "../../../../services/pricing"
|
import PricingService from "../../../../services/pricing"
|
||||||
@@ -23,6 +22,7 @@ import { IsType } from "../../../../utils/validators/is-type"
|
|||||||
import { FlagRouter } from "../../../../utils/flag-router"
|
import { FlagRouter } from "../../../../utils/flag-router"
|
||||||
import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/publishable-api-keys"
|
import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/publishable-api-keys"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
import { Cart, Product } from "../../../../models"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [get] /products
|
* @oas [get] /products
|
||||||
@@ -187,15 +187,16 @@ export default async (req, res) => {
|
|||||||
req.scope.resolve("productVariantInventoryService")
|
req.scope.resolve("productVariantInventoryService")
|
||||||
const pricingService: PricingService = req.scope.resolve("pricingService")
|
const pricingService: PricingService = req.scope.resolve("pricingService")
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
const regionService: RegionService = req.scope.resolve("regionService")
|
|
||||||
|
|
||||||
const validated = req.validatedQuery as StoreGetProductsParams
|
const validated = req.validatedQuery as StoreGetProductsParams
|
||||||
|
|
||||||
let {
|
let {
|
||||||
cart_id,
|
cart_id,
|
||||||
region_id: regionId,
|
region_id: regionId,
|
||||||
currency_code: currencyCode,
|
currency_code: currencyCode,
|
||||||
...filterableFields
|
...filterableFields
|
||||||
} = req.filterableFields
|
} = req.filterableFields
|
||||||
|
|
||||||
const listConfig = req.listConfig
|
const listConfig = req.listConfig
|
||||||
|
|
||||||
// get only published products for store endpoint
|
// get only published products for store endpoint
|
||||||
@@ -212,37 +213,50 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [rawProducts, count] = await productService.listAndCount(
|
const promises: Promise<any>[] = []
|
||||||
filterableFields,
|
|
||||||
listConfig
|
promises.push(productService.listAndCount(filterableFields, listConfig))
|
||||||
)
|
|
||||||
|
|
||||||
if (validated.cart_id) {
|
if (validated.cart_id) {
|
||||||
const cart = await cartService.retrieve(validated.cart_id, {
|
promises.push(
|
||||||
select: ["id", "region_id"],
|
cartService.retrieve(validated.cart_id, {
|
||||||
})
|
select: ["id", "region_id"] as any,
|
||||||
const region = await regionService.retrieve(cart.region_id, {
|
relations: ["region"],
|
||||||
select: ["id", "currency_code"],
|
})
|
||||||
})
|
)
|
||||||
regionId = region.id
|
|
||||||
currencyCode = region.currency_code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pricedProducts = await pricingService.setProductPrices(rawProducts, {
|
const [[rawProducts, count], cart] = (await Promise.all(promises)) as [
|
||||||
cart_id: cart_id,
|
[Product[], number],
|
||||||
region_id: regionId,
|
Cart
|
||||||
currency_code: currencyCode,
|
]
|
||||||
customer_id: req.user?.customer_id,
|
|
||||||
include_discount_prices: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const products = await productVariantInventoryService.setProductAvailability(
|
if (validated.cart_id) {
|
||||||
pricedProducts,
|
regionId = cart.region_id
|
||||||
filterableFields.sales_channel_id
|
currencyCode = cart.region.currency_code
|
||||||
)
|
}
|
||||||
|
|
||||||
|
// Create a new reference just for naming purpose
|
||||||
|
const computedProducts = rawProducts
|
||||||
|
|
||||||
|
// We can run them concurrently as the new properties are assigned to the references
|
||||||
|
// of the appropriate entity
|
||||||
|
await Promise.all([
|
||||||
|
pricingService.setProductPrices(computedProducts, {
|
||||||
|
cart_id: cart_id,
|
||||||
|
region_id: regionId,
|
||||||
|
currency_code: currencyCode,
|
||||||
|
customer_id: req.user?.customer_id,
|
||||||
|
include_discount_prices: true,
|
||||||
|
}),
|
||||||
|
productVariantInventoryService.setProductAvailability(
|
||||||
|
computedProducts,
|
||||||
|
filterableFields.sales_channel_id
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
products: cleanResponseData(products, req.allowedProperties || []),
|
products: cleanResponseData(computedProducts, req.allowedProperties || []),
|
||||||
count,
|
count,
|
||||||
offset: validated.offset,
|
offset: validated.offset,
|
||||||
limit: validated.limit,
|
limit: validated.limit,
|
||||||
|
|||||||
@@ -98,10 +98,10 @@ class PricingService extends TransactionBaseService {
|
|||||||
* @param productRates - the tax rates that the product has applied
|
* @param productRates - the tax rates that the product has applied
|
||||||
* @return The tax related variant prices.
|
* @return The tax related variant prices.
|
||||||
*/
|
*/
|
||||||
async calculateTaxes(
|
calculateTaxes(
|
||||||
variantPricing: ProductVariantPricing,
|
variantPricing: ProductVariantPricing,
|
||||||
productRates: TaxServiceRate[]
|
productRates: TaxServiceRate[]
|
||||||
): Promise<TaxedPricing> {
|
): TaxedPricing {
|
||||||
const rate = productRates.reduce(
|
const rate = productRates.reduce(
|
||||||
(accRate: number, nextTaxRate: TaxServiceRate) => {
|
(accRate: number, nextTaxRate: TaxServiceRate) => {
|
||||||
return accRate + (nextTaxRate.rate || 0) / 100
|
return accRate + (nextTaxRate.rate || 0) / 100
|
||||||
@@ -167,6 +167,10 @@ class PricingService extends TransactionBaseService {
|
|||||||
): Promise<ProductVariantPricing> {
|
): Promise<ProductVariantPricing> {
|
||||||
context.price_selection.tax_rates = taxRates
|
context.price_selection.tax_rates = taxRates
|
||||||
|
|
||||||
|
// TODO: Should think about updating the price strategy to take
|
||||||
|
// a collection of variantId so that the strategy can do a bulk computation
|
||||||
|
// and therefore improve the overall perf. Then the method can return a map
|
||||||
|
// of variant pricing Map<id, variant pricing>
|
||||||
const pricing = await this.priceSelectionStrategy
|
const pricing = await this.priceSelectionStrategy
|
||||||
.withTransaction(this.activeManager_)
|
.withTransaction(this.activeManager_)
|
||||||
.calculateVariantPrice(variantId, context.price_selection)
|
.calculateVariantPrice(variantId, context.price_selection)
|
||||||
@@ -186,7 +190,7 @@ class PricingService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.automatic_taxes && context.price_selection.region_id) {
|
if (context.automatic_taxes && context.price_selection.region_id) {
|
||||||
const taxResults = await this.calculateTaxes(pricingResult, taxRates)
|
const taxResults = this.calculateTaxes(pricingResult, taxRates)
|
||||||
|
|
||||||
pricingResult.original_price_incl_tax = taxResults.original_price_incl_tax
|
pricingResult.original_price_incl_tax = taxResults.original_price_incl_tax
|
||||||
pricingResult.calculated_price_incl_tax =
|
pricingResult.calculated_price_incl_tax =
|
||||||
@@ -360,6 +364,8 @@ class PricingService extends TransactionBaseService {
|
|||||||
const pricings = {}
|
const pricings = {}
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
variants.map(async ({ id }) => {
|
variants.map(async ({ id }) => {
|
||||||
|
// TODO: Depending on the todo inside the getProductVariantPricing_ we would just have
|
||||||
|
// to return the map
|
||||||
const variantPricing = await this.getProductVariantPricing_(
|
const variantPricing = await this.getProductVariantPricing_(
|
||||||
id,
|
id,
|
||||||
taxRates,
|
taxRates,
|
||||||
@@ -451,28 +457,21 @@ class PricingService extends TransactionBaseService {
|
|||||||
return product
|
return product
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Depending on the todo in getProductPricing_ update this method to
|
||||||
|
// consume the map to assign the data to the variants
|
||||||
const variantPricing = await this.getProductPricing_(
|
const variantPricing = await this.getProductPricing_(
|
||||||
product.id,
|
product.id,
|
||||||
product.variants,
|
product.variants,
|
||||||
pricingContext
|
pricingContext
|
||||||
)
|
)
|
||||||
|
|
||||||
const pricedVariants = product.variants.map(
|
product.variants.map((productVariant): PricedVariant => {
|
||||||
(productVariant): PricedVariant => {
|
const pricing = variantPricing[productVariant.id]
|
||||||
const pricing = variantPricing[productVariant.id]
|
Object.assign(productVariant, pricing)
|
||||||
return {
|
return productVariant as unknown as PricedVariant
|
||||||
...productVariant,
|
})
|
||||||
...pricing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const pricedProduct = {
|
return product
|
||||||
...product,
|
|
||||||
variants: pricedVariants,
|
|
||||||
}
|
|
||||||
|
|
||||||
return pricedProduct
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user