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:
co-authored by
Zakaria El Asri
parent
0b2a3a0f0e
commit
a69b52e031
@@ -1,3 +1,4 @@
|
||||
const { PriceList, CustomerGroup } = require("@medusajs/medusa")
|
||||
const path = require("path")
|
||||
|
||||
const setupServer = require("../../../helpers/setup-server")
|
||||
@@ -8,6 +9,9 @@ const {
|
||||
simpleProductFactory,
|
||||
simplePriceListFactory,
|
||||
} = require("../../factories")
|
||||
const {
|
||||
simpleCustomerGroupFactory,
|
||||
} = require("../../factories/simple-customer-group-factory")
|
||||
const adminSeeder = require("../../helpers/admin-seeder")
|
||||
const customerSeeder = require("../../helpers/customer-seeder")
|
||||
const priceListSeeder = require("../../helpers/price-list-seeder")
|
||||
@@ -322,6 +326,48 @@ describe("/admin/price-lists", () => {
|
||||
)
|
||||
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", () => {
|
||||
|
||||
@@ -22,7 +22,7 @@ export const simpleCustomerGroupFactory = async (
|
||||
data.id || `simple-customer-group-${Math.random() * 1000}`
|
||||
const c = manager.create(CustomerGroup, {
|
||||
id: customerGroupId,
|
||||
name: data.name,
|
||||
name: data.name || faker.company.companyName(),
|
||||
})
|
||||
|
||||
const group = await manager.save(c)
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@medusajs/medusa"
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
import { simpleCustomerGroupFactory } from "./simple-customer-group-factory"
|
||||
|
||||
type ProductListPrice = {
|
||||
variant_id: string
|
||||
@@ -42,22 +43,10 @@ export const simplePriceListFactory = async (
|
||||
|
||||
let customerGroups = []
|
||||
if (typeof data.customer_groups !== "undefined") {
|
||||
await manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(CustomerGroup)
|
||||
.values(
|
||||
data.customer_groups.map((group) => ({
|
||||
id: group,
|
||||
name: faker.company.companyName(),
|
||||
}))
|
||||
customerGroups = await Promise.all(
|
||||
data.customer_groups.map((group) =>
|
||||
simpleCustomerGroupFactory(connection, { id: group })
|
||||
)
|
||||
.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("/", middlewares.wrap(require("./list-price-lists").default))
|
||||
route.get(
|
||||
"/",
|
||||
middlewares.normalizeQuery(),
|
||||
middlewares.wrap(require("./list-price-lists").default)
|
||||
)
|
||||
|
||||
route.get(
|
||||
"/:id/products",
|
||||
|
||||
@@ -2,22 +2,26 @@ import { groupBy, map } from "lodash"
|
||||
import {
|
||||
Brackets,
|
||||
EntityRepository,
|
||||
FindManyOptions, Repository
|
||||
FindManyOptions,
|
||||
FindOperator,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
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)
|
||||
export class PriceListRepository extends Repository<PriceList> {
|
||||
public async getFreeTextSearchResultsAndCount(
|
||||
q: string,
|
||||
options: PriceListFindOptions = { where: {} },
|
||||
groups?: FindOperator<PriceList>,
|
||||
relations: (keyof PriceList)[] = []
|
||||
): Promise<[PriceList[], number]> {
|
||||
options.where = options.where ?? {}
|
||||
let qb = this.createQueryBuilder("price_list")
|
||||
|
||||
const qb = this.createQueryBuilder("price_list")
|
||||
.leftJoinAndSelect("price_list.customer_groups", "customer_group")
|
||||
.select(["price_list.id"])
|
||||
.where(options.where)
|
||||
@@ -31,6 +35,10 @@ export class PriceListRepository extends Repository<PriceList> {
|
||||
.skip(options.skip)
|
||||
.take(options.take)
|
||||
|
||||
if (groups) {
|
||||
qb.andWhere("group.id IN (:...ids)", { ids: groups.value })
|
||||
}
|
||||
|
||||
const [results, count] = await qb.getManyAndCount()
|
||||
|
||||
const price_lists = await this.findWithRelations(
|
||||
@@ -53,7 +61,6 @@ export class PriceListRepository extends Repository<PriceList> {
|
||||
} else {
|
||||
entities = await this.find(idsOrOptionsWithoutRelations)
|
||||
}
|
||||
|
||||
const groupedRelations: Record<string, string[]> = {}
|
||||
for (const relation of relations) {
|
||||
const [topLevel] = relation.split(".")
|
||||
@@ -63,7 +70,6 @@ export class PriceListRepository extends Repository<PriceList> {
|
||||
groupedRelations[topLevel] = [relation]
|
||||
}
|
||||
}
|
||||
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.values(groupedRelations).map((relations: string[]) => {
|
||||
@@ -72,7 +78,7 @@ export class PriceListRepository extends Repository<PriceList> {
|
||||
relations: relations as string[],
|
||||
})
|
||||
})
|
||||
).then(entitiesIdsWithRelations => entitiesIdsWithRelations.flat())
|
||||
).then((entitiesIdsWithRelations) => entitiesIdsWithRelations.flat())
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
@@ -87,9 +93,31 @@ export class PriceListRepository extends Repository<PriceList> {
|
||||
): Promise<PriceList | undefined> {
|
||||
options.take = 1
|
||||
|
||||
return (await this.findWithRelations(
|
||||
relations,
|
||||
options
|
||||
))?.pop()
|
||||
return (await this.findWithRelations(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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,10 +252,18 @@ class PriceListService extends BaseService {
|
||||
selector: FilterablePriceListProps = {},
|
||||
config: FindConfig<PriceList> = { skip: 0, take: 20 }
|
||||
): 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)
|
||||
return await priceListRepo.find(query)
|
||||
const query = this.buildQuery_(selector, config)
|
||||
|
||||
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 = {},
|
||||
config: FindConfig<PriceList> = { skip: 0, take: 20 }
|
||||
): Promise<[PriceList[], number]> {
|
||||
const priceListRepo = this.manager_.getCustomRepository(this.priceListRepo_)
|
||||
const q = selector.q
|
||||
const { relations, ...query } = this.buildQuery_(selector, config)
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const priceListRepo = manager.getCustomRepository(this.priceListRepo_)
|
||||
const q = selector.q
|
||||
const { relations, ...query } = this.buildQuery_(selector, config)
|
||||
|
||||
if (q) {
|
||||
delete query.where.q
|
||||
return await priceListRepo.getFreeTextSearchResultsAndCount(
|
||||
q,
|
||||
query,
|
||||
relations
|
||||
)
|
||||
}
|
||||
return await priceListRepo.findAndCount({ ...query, relations })
|
||||
const groups = query.where.customer_groups
|
||||
delete query.where.customer_groups
|
||||
|
||||
if (q) {
|
||||
delete query.where.q
|
||||
return await priceListRepo.getFreeTextSearchResultsAndCount(
|
||||
q,
|
||||
query,
|
||||
groups,
|
||||
relations
|
||||
)
|
||||
}
|
||||
return await priceListRepo.listAndCount({ ...query, relations }, groups)
|
||||
})
|
||||
}
|
||||
|
||||
async upsertCustomerGroups_(
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { PriceList } from "../models/price-list"
|
||||
import { DateComparisonOperator } from "./common"
|
||||
import { DateComparisonOperator, FindConfig } from "./common"
|
||||
import { XorConstraint } from "./validators/xor"
|
||||
|
||||
export enum PriceListType {
|
||||
@@ -39,6 +39,10 @@ export class FilterablePriceListProps {
|
||||
@IsOptional()
|
||||
name?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ each: true })
|
||||
customer_groups?: string[]
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string
|
||||
|
||||
Reference in New Issue
Block a user