feat: 수주등록 모달 및 범용 컴포넌트 개발
- 범용 컴포넌트 3종 개발 및 레지스트리 등록: * AutocompleteSearchInput: 자동완성 검색 입력 컴포넌트 * EntitySearchInput: 엔티티 검색 모달 컴포넌트 * ModalRepeaterTable: 모달 기반 반복 테이블 컴포넌트 - 수주등록 전용 컴포넌트: * OrderCustomerSearch: 거래처 검색 (AutocompleteSearchInput 래퍼) * OrderItemRepeaterTable: 품목 관리 (ModalRepeaterTable 래퍼) * OrderRegistrationModal: 수주등록 메인 모달 - 백엔드 API: * Entity 검색 API (멀티테넌시 지원) * 수주 등록 API (자동 채번) - 화면 편집기 통합: * 컴포넌트 레지스트리에 등록 * ConfigPanel을 통한 설정 기능 * 드래그앤드롭으로 배치 가능 - 개발 문서: * 수주등록_화면_개발_계획서.md (상세 설계 문서)
This commit is contained in:
109
backend-node/src/controllers/entitySearchController.ts
Normal file
109
backend-node/src/controllers/entitySearchController.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Request, Response } from "express";
|
||||
import { getPool } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* 엔티티 검색 API
|
||||
* GET /api/entity-search/:tableName
|
||||
*/
|
||||
export async function searchEntity(req: Request, res: Response) {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
const {
|
||||
searchText = "",
|
||||
searchFields = "",
|
||||
filterCondition = "{}",
|
||||
page = "1",
|
||||
limit = "20",
|
||||
} = req.query;
|
||||
|
||||
// 멀티테넌시
|
||||
const companyCode = req.user!.companyCode;
|
||||
|
||||
// 검색 필드 파싱
|
||||
const fields = searchFields
|
||||
? (searchFields as string).split(",").map((f) => f.trim())
|
||||
: [];
|
||||
|
||||
// WHERE 조건 생성
|
||||
const whereConditions: string[] = [];
|
||||
const params: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
// 멀티테넌시 필터링
|
||||
if (companyCode !== "*") {
|
||||
whereConditions.push(`company_code = $${paramIndex}`);
|
||||
params.push(companyCode);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 검색 조건
|
||||
if (searchText && fields.length > 0) {
|
||||
const searchConditions = fields.map((field) => {
|
||||
const condition = `${field}::text ILIKE $${paramIndex}`;
|
||||
paramIndex++;
|
||||
return condition;
|
||||
});
|
||||
whereConditions.push(`(${searchConditions.join(" OR ")})`);
|
||||
|
||||
// 검색어 파라미터 추가
|
||||
fields.forEach(() => {
|
||||
params.push(`%${searchText}%`);
|
||||
});
|
||||
}
|
||||
|
||||
// 추가 필터 조건
|
||||
const additionalFilter = JSON.parse(filterCondition as string);
|
||||
for (const [key, value] of Object.entries(additionalFilter)) {
|
||||
whereConditions.push(`${key} = $${paramIndex}`);
|
||||
params.push(value);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 페이징
|
||||
const offset = (parseInt(page as string) - 1) * parseInt(limit as string);
|
||||
const whereClause =
|
||||
whereConditions.length > 0
|
||||
? `WHERE ${whereConditions.join(" AND ")}`
|
||||
: "";
|
||||
|
||||
// 쿼리 실행
|
||||
const pool = getPool();
|
||||
const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
|
||||
const dataQuery = `
|
||||
SELECT * FROM ${tableName} ${whereClause}
|
||||
ORDER BY id DESC
|
||||
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
||||
`;
|
||||
|
||||
params.push(parseInt(limit as string));
|
||||
params.push(offset);
|
||||
|
||||
const countResult = await pool.query(
|
||||
countQuery,
|
||||
params.slice(0, params.length - 2)
|
||||
);
|
||||
const dataResult = await pool.query(dataQuery, params);
|
||||
|
||||
logger.info("엔티티 검색 성공", {
|
||||
tableName,
|
||||
searchText,
|
||||
companyCode,
|
||||
rowCount: dataResult.rowCount,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: dataResult.rows,
|
||||
pagination: {
|
||||
total: parseInt(countResult.rows[0].count),
|
||||
page: parseInt(page as string),
|
||||
limit: parseInt(limit as string),
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("엔티티 검색 오류", { error: error.message, stack: error.stack });
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user