import { Request, Response } from "express"; import { query, queryOne } from "../database/db"; import { logger } from "../utils/logger"; export interface EntityReferenceOption { value: string; label: string; } export interface EntityReferenceData { options: EntityReferenceOption[]; referenceInfo: { referenceTable: string; referenceColumn: string; displayColumn: string | null; }; } export interface CodeReferenceData { options: EntityReferenceOption[]; codeCategory: string; } export class EntityReferenceController { /** * 엔티티 참조 데이터 조회 * GET /api/entity-reference/:tableName/:columnName */ static async getEntityReferenceData(req: Request, res: Response) { try { const { tableName, columnName } = req.params; const { limit = 100, search } = req.query; // 멀티테넌시: 인증된 사용자의 회사 코드 const companyCode = (req as any).user?.companyCode; logger.info(`엔티티 참조 데이터 조회 요청: ${tableName}.${columnName}`, { limit, search, companyCode, }); // 컬럼 정보 조회 (table_type_columns에서) const columnInfo = await queryOne( `SELECT * FROM table_type_columns WHERE table_name = $1 AND column_name = $2 AND company_code = '*' LIMIT 1`, [tableName, columnName] ); if (!columnInfo) { return res.status(404).json({ success: false, message: `컬럼 정보를 찾을 수 없습니다: ${tableName}.${columnName}`, }); } // inputType 확인 if (columnInfo.input_type !== "entity") { return res.status(400).json({ success: false, message: `컬럼 '${tableName}.${columnName}'은 entity 타입이 아닙니다. inputType: ${columnInfo.input_type}`, }); } // table_type_columns에서 직접 참조 정보 가져오기 const referenceTable = columnInfo.reference_table; const referenceColumn = columnInfo.reference_column; const displayColumn = columnInfo.display_column || "name"; // entity 타입인데 참조 테이블 정보가 없으면 오류 if (!referenceTable || !referenceColumn) { return res.status(400).json({ success: false, message: `Entity 타입 컬럼 '${tableName}.${columnName}'에 참조 테이블 정보가 설정되지 않았습니다. table_type_columns에서 reference_table과 reference_column을 확인해주세요.`, }); } // 참조 테이블이 실제로 존재하는지 확인 try { await query(`SELECT 1 FROM ${referenceTable} LIMIT 1`); logger.info( `Entity 참조 설정: ${tableName}.${columnName} -> ${referenceTable}.${referenceColumn} (display: ${displayColumn})` ); } catch (error) { logger.error( `참조 테이블 '${referenceTable}'이 존재하지 않습니다:`, error ); return res.status(400).json({ success: false, message: `참조 테이블 '${referenceTable}'이 존재하지 않습니다. table_type_columns의 reference_table 설정을 확인해주세요.`, }); } // 참조 테이블에 company_code 컬럼이 있는지 확인 const hasCompanyCode = await queryOne( `SELECT column_name FROM information_schema.columns WHERE table_name = $1 AND column_name = 'company_code' AND table_schema = 'public'`, [referenceTable] ); // 동적 쿼리로 참조 데이터 조회 (멀티테넌시 필터 적용) const whereConditions: string[] = []; const queryParams: any[] = []; // 멀티테넌시: company_code 필터링 (참조 테이블에 company_code가 있는 경우) if (hasCompanyCode && companyCode && companyCode !== "*") { queryParams.push(companyCode); whereConditions.push(`company_code = $${queryParams.length}`); logger.info(`멀티테넌시 필터 적용: company_code = ${companyCode}`, { referenceTable }); } // 검색 조건 추가 if (search) { queryParams.push(`%${search}%`); whereConditions.push(`${displayColumn} ILIKE $${queryParams.length}`); } let sqlQuery = `SELECT ${referenceColumn}, ${displayColumn} as display_name FROM ${referenceTable}`; if (whereConditions.length > 0) { sqlQuery += ` WHERE ${whereConditions.join(" AND ")}`; } sqlQuery += ` ORDER BY ${displayColumn} LIMIT $${queryParams.length + 1}`; queryParams.push(Number(limit)); logger.info(`실행할 쿼리: ${sqlQuery}`, { queryParams, referenceTable, referenceColumn, displayColumn, companyCode, }); const referenceData = await query(sqlQuery, queryParams); // 옵션 형태로 변환 const options: EntityReferenceOption[] = (referenceData as any[]).map( (row) => ({ value: String(row[referenceColumn]), label: String(row.display_name || row[referenceColumn]), }) ); logger.info(`엔티티 참조 데이터 조회 완료: ${options.length}개 항목`, { companyCode }); return res.json({ success: true, data: { options, referenceInfo: { referenceTable, referenceColumn, displayColumn, }, }, }); } catch (error) { logger.error("엔티티 참조 데이터 조회 실패:", error); return res.status(500).json({ success: false, message: "엔티티 참조 데이터 조회 중 오류가 발생했습니다.", }); } } /** * 공통 코드 데이터 조회 * GET /api/entity-reference/code/:codeCategory */ static async getCodeData(req: Request, res: Response) { try { const { codeCategory } = req.params; const { limit = 100, search } = req.query; // 멀티테넌시: 인증된 사용자의 회사 코드 const companyCode = (req as any).user?.companyCode; logger.info(`공통 코드 데이터 조회 요청: ${codeCategory}`, { limit, search, companyCode, }); // code_info 테이블에서 코드 데이터 조회 (멀티테넌시 필터 적용) const queryParams: any[] = [codeCategory, 'Y']; let sqlQuery = ` SELECT code_value, code_name FROM code_info WHERE code_category = $1 AND is_active = $2 `; // 멀티테넌시: company_code 필터링 if (companyCode && companyCode !== "*") { queryParams.push(companyCode); sqlQuery += ` AND company_code = $${queryParams.length}`; logger.info(`공통 코드 멀티테넌시 필터 적용: company_code = ${companyCode}`); } if (search) { queryParams.push(`%${search}%`); sqlQuery += ` AND code_name ILIKE $${queryParams.length}`; } sqlQuery += ` ORDER BY code_name ASC LIMIT $${queryParams.length + 1}`; queryParams.push(Number(limit)); const codeData = await query(sqlQuery, queryParams); // 옵션 형태로 변환 const options: EntityReferenceOption[] = codeData.map((code: any) => ({ value: code.code_value, label: code.code_name, })); logger.info(`공통 코드 데이터 조회 완료: ${options.length}개 항목`, { companyCode }); return res.json({ success: true, data: { options, codeCategory, }, }); } catch (error) { logger.error("공통 코드 데이터 조회 실패:", error); return res.status(500).json({ success: false, message: "공통 코드 데이터 조회 중 오류가 발생했습니다.", }); } } }