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:
committed by
GitHub
parent
5f76b967d9
commit
f1512605d2
5
.changeset/gentle-guests-brush.md
Normal file
5
.changeset/gentle-guests-brush.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
feat(medusa): admin list product with product isolated module
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user