feat(medusa): Admin list product with product isolated module (#5046)

**What**
First iteration:
Admin list products return everything but does not include proper filtering by discount id or price list id. Also, same as the previous pr, the fields/expand is not included

depends on https://github.com/medusajs/medusa/pull/5130
This commit is contained in:
Adrien de Peretti
2023-09-22 13:14:23 +02:00
committed by GitHub
parent 5f76b967d9
commit f1512605d2
4 changed files with 196 additions and 7 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
feat(medusa): admin list product with product isolated module

View File

@@ -1,5 +1,6 @@
import { IsNumber, IsOptional, IsString } from "class-validator"
import {
PriceListService,
PricingService,
ProductService,
ProductVariantInventoryService,
@@ -11,6 +12,8 @@ import { IInventoryService } from "@medusajs/types"
import { PricedProduct } from "../../../../types/pricing"
import { Product } from "../../../../models"
import { Type } from "class-transformer"
import IsolateProductDomainFeatureFlag from "../../../../loaders/feature-flags/isolate-product-domain"
import { defaultAdminProductRemoteQueryObject } from "./index"
/**
* @oas [get] /admin/products
@@ -235,16 +238,33 @@ export default async (req, res) => {
const salesChannelService: SalesChannelService = req.scope.resolve(
"salesChannelService"
)
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
const pricingService: PricingService = req.scope.resolve("pricingService")
const { skip, take, relations } = req.listConfig
const manager = req.scope.resolve("manager")
let rawProducts
let count
const [rawProducts, count] = await productService.listAndCount(
req.filterableFields,
req.listConfig
)
if (featureFlagRouter.isFeatureEnabled(IsolateProductDomainFeatureFlag.key)) {
const [products, count_] =
await listAndCountProductWithIsolatedProductModule(
req,
req.filterableFields,
req.listConfig
)
rawProducts = products
count = count_
} else {
const [products, count_] = await productService.listAndCount(
req.filterableFields,
req.listConfig
)
rawProducts = products
count = count_
}
let products: (Product | PricedProduct)[] = rawProducts
@@ -280,6 +300,118 @@ export default async (req, res) => {
})
}
async function listAndCountProductWithIsolatedProductModule(
req,
filterableFields,
listConfig
) {
// TODO: Add support for fields/expands
const remoteQuery = req.scope.resolve("remoteQuery")
const productIdsFilter: Set<string> = new Set()
const variantIdsFilter: Set<string> = new Set()
const promises: Promise<void>[] = []
// This is not the best way of handling cross filtering but for now I would say it is fine
const salesChannelIdFilter = filterableFields.sales_channel_id
delete filterableFields.sales_channel_id
if (salesChannelIdFilter) {
const salesChannelService = req.scope.resolve(
"salesChannelService"
) as SalesChannelService
promises.push(
salesChannelService
.listProductIdsBySalesChannelIds(salesChannelIdFilter)
.then((productIdsInSalesChannel) => {
let filteredProductIds =
productIdsInSalesChannel[salesChannelIdFilter]
if (filterableFields.id) {
filterableFields.id = Array.isArray(filterableFields.id)
? filterableFields.id
: [filterableFields.id]
const salesChannelProductIdsSet = new Set(filteredProductIds)
filteredProductIds = filterableFields.id.filter((productId) =>
salesChannelProductIdsSet.has(productId)
)
}
filteredProductIds.map((id) => productIdsFilter.add(id))
})
)
}
const priceListId = filterableFields.price_list_id
delete filterableFields.price_list_id
if (priceListId) {
// TODO: it is working but validate the behaviour.
// e.g pricing context properly set.
// At the moment filtering by price list but not having any customer id or
// include discount forces the query to filter with price list id is null
const priceListService = req.scope.resolve(
"priceListService"
) as PriceListService
promises.push(
priceListService
.listPriceListsVariantIdsMap(priceListId)
.then((priceListVariantIdsMap) => {
priceListVariantIdsMap[priceListId].map((variantId) =>
variantIdsFilter.add(variantId)
)
})
)
}
const discountConditionId = filterableFields.discount_condition_id
delete filterableFields.discount_condition_id
if (discountConditionId) {
// TODO implement later
}
await Promise.all(promises)
if (productIdsFilter.size > 0) {
filterableFields.id = Array.from(productIdsFilter)
}
if (variantIdsFilter.size > 0) {
filterableFields.variants = { id: Array.from(variantIdsFilter) }
}
const variables = {
filters: filterableFields,
order: listConfig.order,
skip: listConfig.skip,
take: listConfig.take,
}
const query = {
product: {
__args: variables,
...defaultAdminProductRemoteQueryObject,
},
}
const {
rows: products,
metadata: { count },
} = await remoteQuery(query)
products.forEach((product) => {
product.profile_id = product.profile?.id
})
return [products, count]
}
export class AdminGetProductsParams extends FilterableProductProps {
@IsNumber()
@IsOptional()

View File

@@ -1,5 +1,5 @@
import { FindOperator, FindOptionsWhere, ILike, In } from "typeorm"
import { PriceList } from "../models"
import { PriceList, ProductVariantMoneyAmount } from "../models"
import { ExtendedFindConfig } from "../types/common"
import { dataSource } from "../loaders/database"
@@ -54,6 +54,29 @@ export const PriceListRepository = dataSource.getRepository(PriceList).extend({
return await Promise.all([this.find(query_), this.count(query_)])
},
async listPriceListsVariantIdsMap(
priceListIds: string | string[]
): Promise<{ [priceListId: string]: string[] }> {
priceListIds = Array.isArray(priceListIds) ? priceListIds : [priceListIds]
const data = await this.createQueryBuilder("pl")
.innerJoin("pl.prices", "prices")
.innerJoinAndSelect(
ProductVariantMoneyAmount,
"pvma",
"pvma.money_amount_id = prices.id"
)
.where("pl.id IN (:...ids)", { ids: priceListIds })
.execute()
return data.reduce((acc, curr) => {
acc[curr["pl_id"]] ??= []
acc[curr["pl_id"]].push(curr["pvma_variant_id"])
acc[curr["pl_id"]] = [...new Set(acc[curr["pl_id"]])]
return acc
}, {})
},
})
export default PriceListRepository

View File

@@ -8,7 +8,7 @@ import {
import { CustomerGroup, PriceList, Product, ProductVariant } from "../models"
import { DeepPartial, EntityManager } from "typeorm"
import { FindConfig, Selector } from "../types/common"
import { MedusaError, isDefined } from "medusa-core-utils"
import { isDefined, MedusaError } from "medusa-core-utils"
import { CustomerGroupService } from "."
import { FilterableProductProps } from "../types/product"
@@ -106,6 +106,35 @@ class PriceListService extends TransactionBaseService {
return priceList
}
async listPriceListsVariantIdsMap(
priceListIds: string | string[]
): Promise<{ [priceListId: string]: string[] }> {
priceListIds = Array.isArray(priceListIds) ? priceListIds : [priceListIds]
if (!priceListIds.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`"priceListIds" must be defined`
)
}
const priceListRepo = this.activeManager_.withRepository(
this.priceListRepo_
)
const priceListsVariantIdsMap =
await priceListRepo.listPriceListsVariantIdsMap(priceListIds)
if (!Object.keys(priceListsVariantIdsMap)?.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`No PriceLists found with ids: ${priceListIds.join(", ")}`
)
}
return priceListsVariantIdsMap
}
/**
* Creates a Price List
* @param priceListObject - the Price List to create