Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 21 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gentle-guests-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---

feat(medusa): admin list product with product isolated module
142 changes: 137 additions & 5 deletions packages/medusa/src/api/routes/admin/products/list-products.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IsNumber, IsOptional, IsString } from "class-validator"
import {
PriceListService,
PricingService,
ProductService,
ProductVariantInventoryService,
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
25 changes: 24 additions & 1 deletion packages/medusa/src/repositories/price-list.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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
31 changes: 30 additions & 1 deletion packages/medusa/src/services/price-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down