Feat: Filter price lists by customer group (#1431)

* add customer groups to price list factory

* add integration test for filtering price lists by customer group

* normalize list price list query

* add customer groups to list-price-list queryparameters

* query based on customergroups if they exist for price lists

* remove verbose flag

* add another price list with a customer group

* remove console.log

* pr feedback

* add query type to repository

* add query type to repository

* set groups to undefined instead of deleting parameter

* remove wildcard destructing

* make buildQuery type specific to price lists

* steal Adriens types

* fix(medusa): support searching for price lists (#1407)

* delete instead of settting groups to undefined

* add groups to query with q

* use simple customer group factory instead of manual creation

* Update simple-customer-group-factory.ts

* remove comma that breaks integration-tests

Co-authored-by: Zakaria El Asri <33696020+zakariaelas@users.noreply.github.com>
This commit is contained in:
Philip KorsholmandZakaria El Asri authored and GitHub committed 2022-05-12 04:22:46 +02:00
1 parent 0b2a3a0f0e
commit a69b52e031
7 files changed
+129 -44

No files matched your search

@@ -1,3 +1,4 @@
const { PriceList, CustomerGroup } = require("@medusajs/medusa")
const path = require("path") const path = require("path")
const setupServer = require("../../../helpers/setup-server") const setupServer = require("../../../helpers/setup-server")
@@ -8,6 +9,9 @@ const {
simpleProductFactory, simpleProductFactory,
simplePriceListFactory, simplePriceListFactory,
} = require("../../factories") } = require("../../factories")
const {
simpleCustomerGroupFactory,
} = require("../../factories/simple-customer-group-factory")
const adminSeeder = require("../../helpers/admin-seeder") const adminSeeder = require("../../helpers/admin-seeder")
const customerSeeder = require("../../helpers/customer-seeder") const customerSeeder = require("../../helpers/customer-seeder")
const priceListSeeder = require("../../helpers/price-list-seeder") const priceListSeeder = require("../../helpers/price-list-seeder")
@@ -322,6 +326,48 @@ describe("/admin/price-lists", () => {
) )
expect(response.data.count).toEqual(1) expect(response.data.count).toEqual(1)
}) })
it("lists only price lists with customer_group", async () => {
await customerSeeder(dbConnection)
await simplePriceListFactory(dbConnection, {
id: "test-list-cgroup-1",
customer_groups: ["customer-group-1"],
})
await simplePriceListFactory(dbConnection, {
id: "test-list-cgroup-2",
customer_groups: ["customer-group-2"],
})
await simplePriceListFactory(dbConnection, {
id: "test-list-cgroup-3",
customer_groups: ["customer-group-3"],
})
await simplePriceListFactory(dbConnection, {
id: "test-list-no-cgroup",
})
const api = useApi()
const response = await api
.get(
`/admin/price-lists?customer_groups[]=customer-group-1,customer-group-2`,
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.warn(err.response.data)
})
expect(response.status).toEqual(200)
expect(response.data.price_lists.length).toEqual(2)
expect(response.data.price_lists).toEqual([
expect.objectContaining({ id: "test-list-cgroup-1" }),
expect.objectContaining({ id: "test-list-cgroup-2" }),
])
})
}) })
describe("POST /admin/price-lists/:id", () => { describe("POST /admin/price-lists/:id", () => {
@@ -22,7 +22,7 @@ export const simpleCustomerGroupFactory = async (
data.id || `simple-customer-group-${Math.random() * 1000}` data.id || `simple-customer-group-${Math.random() * 1000}`
const c = manager.create(CustomerGroup, { const c = manager.create(CustomerGroup, {
id: customerGroupId, id: customerGroupId,
name: data.name, name: data.name || faker.company.companyName(),
}) })
const group = await manager.save(c) const group = await manager.save(c)
@@ -7,6 +7,7 @@ import {
} from "@medusajs/medusa" } from "@medusajs/medusa"
import faker from "faker" import faker from "faker"
import { Connection } from "typeorm" import { Connection } from "typeorm"
import { simpleCustomerGroupFactory } from "./simple-customer-group-factory"
type ProductListPrice = { type ProductListPrice = {
variant_id: string variant_id: string
@@ -42,22 +43,10 @@ export const simplePriceListFactory = async (
let customerGroups = [] let customerGroups = []
if (typeof data.customer_groups !== "undefined") { if (typeof data.customer_groups !== "undefined") {
await manager customerGroups = await Promise.all(
.createQueryBuilder() data.customer_groups.map((group) =>
.insert() simpleCustomerGroupFactory(connection, { id: group })
.into(CustomerGroup)
.values(
data.customer_groups.map((group) => ({
id: group,
name: faker.company.companyName(),
}))
) )
.orIgnore()
.execute()
customerGroups = await manager.findByIds(
CustomerGroup,
data.customer_groups
) )
} }
@@ -11,7 +11,11 @@ export default (app) => {
route.get("/:id", middlewares.wrap(require("./get-price-list").default)) route.get("/:id", middlewares.wrap(require("./get-price-list").default))
route.get("/", middlewares.wrap(require("./list-price-lists").default)) route.get(
"/",
middlewares.normalizeQuery(),
middlewares.wrap(require("./list-price-lists").default)
)
route.get( route.get(
"/:id/products", "/:id/products",
+39 -11
View File
@@ -2,22 +2,26 @@ import { groupBy, map } from "lodash"
import { import {
Brackets, Brackets,
EntityRepository, EntityRepository,
FindManyOptions, Repository FindManyOptions,
FindOperator,
Repository,
} from "typeorm" } from "typeorm"
import { PriceList } from "../models/price-list" import { PriceList } from "../models/price-list"
import { CustomFindOptions } from "../types/common" import { CustomFindOptions, ExtendedFindConfig } from "../types/common"
type PriceListFindOptions = CustomFindOptions<PriceList, 'status' | 'type'> type PriceListFindOptions = CustomFindOptions<PriceList, "status" | "type">
@EntityRepository(PriceList) @EntityRepository(PriceList)
export class PriceListRepository extends Repository<PriceList> { export class PriceListRepository extends Repository<PriceList> {
public async getFreeTextSearchResultsAndCount( public async getFreeTextSearchResultsAndCount(
q: string, q: string,
options: PriceListFindOptions = { where: {} }, options: PriceListFindOptions = { where: {} },
groups?: FindOperator<PriceList>,
relations: (keyof PriceList)[] = [] relations: (keyof PriceList)[] = []
): Promise<[PriceList[], number]> { ): Promise<[PriceList[], number]> {
options.where = options.where ?? {} options.where = options.where ?? {}
let qb = this.createQueryBuilder("price_list")
const qb = this.createQueryBuilder("price_list")
.leftJoinAndSelect("price_list.customer_groups", "customer_group") .leftJoinAndSelect("price_list.customer_groups", "customer_group")
.select(["price_list.id"]) .select(["price_list.id"])
.where(options.where) .where(options.where)
@@ -31,6 +35,10 @@ export class PriceListRepository extends Repository<PriceList> {
.skip(options.skip) .skip(options.skip)
.take(options.take) .take(options.take)
if (groups) {
qb.andWhere("group.id IN (:...ids)", { ids: groups.value })
}
const [results, count] = await qb.getManyAndCount() const [results, count] = await qb.getManyAndCount()
const price_lists = await this.findWithRelations( const price_lists = await this.findWithRelations(
@@ -53,7 +61,6 @@ export class PriceListRepository extends Repository<PriceList> {
} else { } else {
entities = await this.find(idsOrOptionsWithoutRelations) entities = await this.find(idsOrOptionsWithoutRelations)
} }
const groupedRelations: Record<string, string[]> = {} const groupedRelations: Record<string, string[]> = {}
for (const relation of relations) { for (const relation of relations) {
const [topLevel] = relation.split(".") const [topLevel] = relation.split(".")
@@ -63,7 +70,6 @@ export class PriceListRepository extends Repository<PriceList> {
groupedRelations[topLevel] = [relation] groupedRelations[topLevel] = [relation]
} }
} }
const entitiesIds = entities.map(({ id }) => id) const entitiesIds = entities.map(({ id }) => id)
const entitiesIdsWithRelations = await Promise.all( const entitiesIdsWithRelations = await Promise.all(
Object.values(groupedRelations).map((relations: string[]) => { Object.values(groupedRelations).map((relations: string[]) => {
@@ -72,7 +78,7 @@ export class PriceListRepository extends Repository<PriceList> {
relations: relations as string[], relations: relations as string[],
}) })
}) })
).then(entitiesIdsWithRelations => entitiesIdsWithRelations.flat()) ).then((entitiesIdsWithRelations) => entitiesIdsWithRelations.flat())
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities) const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id") const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
@@ -87,9 +93,31 @@ export class PriceListRepository extends Repository<PriceList> {
): Promise<PriceList | undefined> { ): Promise<PriceList | undefined> {
options.take = 1 options.take = 1
return (await this.findWithRelations( return (await this.findWithRelations(relations, options))?.pop()
relations, }
options
))?.pop() async listAndCount(
query: ExtendedFindConfig<PriceList>,
groups?: FindOperator<PriceList>
): Promise<[PriceList[], number]> {
const qb = this.createQueryBuilder("price_list")
.where(query.where)
.skip(query.skip)
.take(query.take)
if (groups) {
qb.leftJoinAndSelect("price_list.customer_groups", "group").andWhere(
"group.id IN (:...ids)",
{ ids: groups.value }
)
}
if (query.relations?.length) {
query.relations.forEach((rel) => {
qb.leftJoinAndSelect(`price_list.${rel}`, rel)
})
}
return await qb.getManyAndCount()
} }
} }
+29 -15
View File
@@ -252,10 +252,18 @@ class PriceListService extends BaseService {
selector: FilterablePriceListProps = {}, selector: FilterablePriceListProps = {},
config: FindConfig<PriceList> = { skip: 0, take: 20 } config: FindConfig<PriceList> = { skip: 0, take: 20 }
): Promise<PriceList[]> { ): Promise<PriceList[]> {
const priceListRepo = this.manager_.getCustomRepository(this.priceListRepo_) return await this.atomicPhase_(async (manager: EntityManager) => {
const priceListRepo = manager.getCustomRepository(this.priceListRepo_)
const query = this.buildQuery_(selector, config) const query = this.buildQuery_(selector, config)
return await priceListRepo.find(query)
const groups = query.where.customer_groups
query.where.customer_groups = undefined
const [priceLists] = await priceListRepo.listAndCount(query, groups)
return priceLists
})
} }
/** /**
@@ -268,19 +276,25 @@ class PriceListService extends BaseService {
selector: FilterablePriceListProps = {}, selector: FilterablePriceListProps = {},
config: FindConfig<PriceList> = { skip: 0, take: 20 } config: FindConfig<PriceList> = { skip: 0, take: 20 }
): Promise<[PriceList[], number]> { ): Promise<[PriceList[], number]> {
const priceListRepo = this.manager_.getCustomRepository(this.priceListRepo_) return await this.atomicPhase_(async (manager: EntityManager) => {
const q = selector.q const priceListRepo = manager.getCustomRepository(this.priceListRepo_)
const { relations, ...query } = this.buildQuery_(selector, config) const q = selector.q
const { relations, ...query } = this.buildQuery_(selector, config)
if (q) { const groups = query.where.customer_groups
delete query.where.q delete query.where.customer_groups
return await priceListRepo.getFreeTextSearchResultsAndCount(
q, if (q) {
query, delete query.where.q
relations return await priceListRepo.getFreeTextSearchResultsAndCount(
) q,
} query,
return await priceListRepo.findAndCount({ ...query, relations }) groups,
relations
)
}
return await priceListRepo.listAndCount({ ...query, relations }, groups)
})
} }
async upsertCustomerGroups_( async upsertCustomerGroups_(
+5 -1
View File
@@ -9,7 +9,7 @@ import {
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { PriceList } from "../models/price-list" import { PriceList } from "../models/price-list"
import { DateComparisonOperator } from "./common" import { DateComparisonOperator, FindConfig } from "./common"
import { XorConstraint } from "./validators/xor" import { XorConstraint } from "./validators/xor"
export enum PriceListType { export enum PriceListType {
@@ -39,6 +39,10 @@ export class FilterablePriceListProps {
@IsOptional() @IsOptional()
name?: string name?: string
@IsOptional()
@IsString({ each: true })
customer_groups?: string[]
@IsString() @IsString()
@IsOptional() @IsOptional()
description?: string description?: string