Skip to content

Commit 2c77a6f

Browse files
committed
search query module
1 parent f2831af commit 2c77a6f

4 files changed

Lines changed: 152 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Controller, Get, Query } from '@nestjs/common';
2+
import { SearchService } from './search.service';
3+
import { SearchQueryDto } from './dto/search-query.dto';
4+
5+
@Controller('search')
6+
export class SearchController {
7+
constructor(private readonly searchService: SearchService) {}
8+
9+
@Get()
10+
async globalSearch(@Query() query: SearchQueryDto) {
11+
return this.searchService.search(query);
12+
}
13+
}

backend/src/search/search.dto.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { IsOptional, IsString, IsNumber, Min } from 'class-validator';
2+
import { Type } from 'class-transformer';
3+
4+
export class SearchQueryDto {
5+
@IsOptional()
6+
@IsString()
7+
category?: string;
8+
9+
@IsOptional()
10+
@IsString()
11+
supplier?: string;
12+
13+
@IsOptional()
14+
@IsString()
15+
branch?: string;
16+
17+
@IsOptional()
18+
@IsString()
19+
location?: string;
20+
21+
@IsOptional()
22+
@Type(() => Number)
23+
@IsNumber()
24+
@Min(1)
25+
page: number = 1;
26+
27+
@IsOptional()
28+
@Type(() => Number)
29+
@IsNumber()
30+
@Min(1)
31+
limit: number = 10;
32+
33+
@IsOptional()
34+
@IsString()
35+
sortBy?: string; // e.g. name, createdAt
36+
37+
@IsOptional()
38+
@IsString()
39+
order: 'ASC' | 'DESC' = 'ASC';
40+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { SearchService } from './search.service';
4+
import { SearchController } from './search.controller';
5+
import { Asset } from '../assets/asset.entity';
6+
import { Inventory } from '../inventory/inventory.entity';
7+
import { Category } from '../categories/category.entity';
8+
import { Supplier } from '../suppliers/supplier.entity';
9+
import { Branch } from '../branches/branch.entity';
10+
11+
@Module({
12+
imports: [
13+
TypeOrmModule.forFeature([Asset, Inventory, Category, Supplier, Branch]),
14+
],
15+
providers: [SearchService],
16+
controllers: [SearchController],
17+
})
18+
export class SearchModule {}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
import { Repository, SelectQueryBuilder } from 'typeorm';
4+
import { Asset } from '../assets/asset.entity';
5+
import { Inventory } from '../inventory/inventory.entity';
6+
import { SearchQueryDto } from './dto/search-query.dto';
7+
8+
@Injectable()
9+
export class SearchService {
10+
constructor(
11+
@InjectRepository(Asset) private assetRepo: Repository<Asset>,
12+
@InjectRepository(Inventory) private inventoryRepo: Repository<Inventory>,
13+
) {}
14+
15+
async search(dto: SearchQueryDto) {
16+
const { category, supplier, branch, location, page, limit, sortBy, order } = dto;
17+
18+
// Build query for assets
19+
let assetQuery = this.assetRepo
20+
.createQueryBuilder('asset')
21+
.leftJoinAndSelect('asset.category', 'category')
22+
.leftJoinAndSelect('asset.supplier', 'supplier')
23+
.leftJoinAndSelect('asset.branch', 'branch')
24+
.leftJoinAndSelect('asset.location', 'location');
25+
26+
if (category) {
27+
assetQuery.andWhere('category.name ILIKE :category', { category: `%${category}%` });
28+
}
29+
if (supplier) {
30+
assetQuery.andWhere('supplier.name ILIKE :supplier', { supplier: `%${supplier}%` });
31+
}
32+
if (branch) {
33+
assetQuery.andWhere('branch.name ILIKE :branch', { branch: `%${branch}%` });
34+
}
35+
if (location) {
36+
assetQuery.andWhere('location.name ILIKE :location', { location: `%${location}%` });
37+
}
38+
39+
if (sortBy) {
40+
assetQuery.orderBy(`asset.${sortBy}`, order || 'ASC');
41+
}
42+
43+
assetQuery.skip((page - 1) * limit).take(limit);
44+
45+
const [assets, totalAssets] = await assetQuery.getManyAndCount();
46+
47+
// Build query for inventory
48+
let inventoryQuery = this.inventoryRepo
49+
.createQueryBuilder('inventory')
50+
.leftJoinAndSelect('inventory.category', 'category')
51+
.leftJoinAndSelect('inventory.supplier', 'supplier')
52+
.leftJoinAndSelect('inventory.branch', 'branch')
53+
.leftJoinAndSelect('inventory.location', 'location');
54+
55+
if (category) {
56+
inventoryQuery.andWhere('category.name ILIKE :category', { category: `%${category}%` });
57+
}
58+
if (supplier) {
59+
inventoryQuery.andWhere('supplier.name ILIKE :supplier', { supplier: `%${supplier}%` });
60+
}
61+
if (branch) {
62+
inventoryQuery.andWhere('branch.name ILIKE :branch', { branch: `%${branch}%` });
63+
}
64+
if (location) {
65+
inventoryQuery.andWhere('location.name ILIKE :location', { location: `%${location}%` });
66+
}
67+
68+
if (sortBy) {
69+
inventoryQuery.orderBy(`inventory.${sortBy}`, order || 'ASC');
70+
}
71+
72+
inventoryQuery.skip((page - 1) * limit).take(limit);
73+
74+
const [inventories, totalInventories] = await inventoryQuery.getManyAndCount();
75+
76+
return {
77+
assets: { data: assets, total: totalAssets },
78+
inventories: { data: inventories, total: totalInventories },
79+
};
80+
}
81+
}

0 commit comments

Comments
 (0)