컴포넌트 리뉴얼 1.0
This commit is contained in:
@@ -70,7 +70,7 @@ import departmentRoutes from "./routes/departmentRoutes"; // 부서 관리
|
||||
import tableCategoryValueRoutes from "./routes/tableCategoryValueRoutes"; // 카테고리 값 관리
|
||||
import codeMergeRoutes from "./routes/codeMergeRoutes"; // 코드 병합
|
||||
import numberingRuleRoutes from "./routes/numberingRuleRoutes"; // 채번 규칙 관리
|
||||
import entitySearchRoutes from "./routes/entitySearchRoutes"; // 엔티티 검색
|
||||
import entitySearchRoutes, { entityOptionsRouter } from "./routes/entitySearchRoutes"; // 엔티티 검색 및 옵션
|
||||
import screenEmbeddingRoutes from "./routes/screenEmbeddingRoutes"; // 화면 임베딩 및 데이터 전달
|
||||
import vehicleTripRoutes from "./routes/vehicleTripRoutes"; // 차량 운행 이력 관리
|
||||
import driverRoutes from "./routes/driverRoutes"; // 공차중계 운전자 관리
|
||||
@@ -249,6 +249,7 @@ app.use("/api/table-categories", tableCategoryValueRoutes); // 카테고리 값
|
||||
app.use("/api/code-merge", codeMergeRoutes); // 코드 병합
|
||||
app.use("/api/numbering-rules", numberingRuleRoutes); // 채번 규칙 관리
|
||||
app.use("/api/entity-search", entitySearchRoutes); // 엔티티 검색
|
||||
app.use("/api/entity", entityOptionsRouter); // 엔티티 옵션 (UnifiedSelect용)
|
||||
app.use("/api/driver", driverRoutes); // 공차중계 운전자 관리
|
||||
app.use("/api/tax-invoice", taxInvoiceRoutes); // 세금계산서 관리
|
||||
app.use("/api/cascading-relations", cascadingRelationRoutes); // 연쇄 드롭다운 관계 관리
|
||||
|
||||
@@ -3,6 +3,101 @@ import { AuthenticatedRequest } from "../types/auth";
|
||||
import { getPool } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* 엔티티 옵션 조회 API (UnifiedSelect용)
|
||||
* GET /api/entity/:tableName/options
|
||||
*
|
||||
* Query Params:
|
||||
* - value: 값 컬럼 (기본: id)
|
||||
* - label: 표시 컬럼 (기본: name)
|
||||
*/
|
||||
export async function getEntityOptions(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
const { value = "id", label = "name" } = req.query;
|
||||
|
||||
// tableName 유효성 검증
|
||||
if (!tableName || tableName === "undefined" || tableName === "null") {
|
||||
logger.warn("엔티티 옵션 조회 실패: 테이블명이 없음", { tableName });
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "테이블명이 지정되지 않았습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const companyCode = req.user!.companyCode;
|
||||
const pool = getPool();
|
||||
|
||||
// 테이블의 실제 컬럼 목록 조회
|
||||
const columnsResult = await pool.query(
|
||||
`SELECT column_name FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = $1`,
|
||||
[tableName]
|
||||
);
|
||||
const existingColumns = new Set(columnsResult.rows.map((r: any) => r.column_name));
|
||||
|
||||
// 요청된 컬럼 검증
|
||||
const valueColumn = existingColumns.has(value as string) ? value : "id";
|
||||
const labelColumn = existingColumns.has(label as string) ? label : "name";
|
||||
|
||||
// 둘 다 없으면 에러
|
||||
if (!existingColumns.has(valueColumn as string)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `테이블 "${tableName}"에 값 컬럼 "${value}"이 존재하지 않습니다.`,
|
||||
});
|
||||
}
|
||||
|
||||
// label 컬럼이 없으면 value 컬럼을 label로도 사용
|
||||
const effectiveLabelColumn = existingColumns.has(labelColumn as string) ? labelColumn : valueColumn;
|
||||
|
||||
// WHERE 조건 (멀티테넌시)
|
||||
const whereConditions: string[] = [];
|
||||
const params: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (companyCode !== "*" && existingColumns.has("company_code")) {
|
||||
whereConditions.push(`company_code = $${paramIndex}`);
|
||||
params.push(companyCode);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const whereClause = whereConditions.length > 0
|
||||
? `WHERE ${whereConditions.join(" AND ")}`
|
||||
: "";
|
||||
|
||||
// 쿼리 실행 (최대 500개)
|
||||
const query = `
|
||||
SELECT ${valueColumn} as value, ${effectiveLabelColumn} as label
|
||||
FROM ${tableName}
|
||||
${whereClause}
|
||||
ORDER BY ${effectiveLabelColumn} ASC
|
||||
LIMIT 500
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
|
||||
logger.info("엔티티 옵션 조회 성공", {
|
||||
tableName,
|
||||
valueColumn,
|
||||
labelColumn: effectiveLabelColumn,
|
||||
companyCode,
|
||||
rowCount: result.rowCount,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("엔티티 옵션 조회 오류", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔티티 검색 API
|
||||
* GET /api/entity-search/:tableName
|
||||
|
||||
@@ -97,11 +97,16 @@ export async function getColumnList(
|
||||
}
|
||||
|
||||
const tableManagementService = new TableManagementService();
|
||||
|
||||
// 🔥 캐시 버스팅: _t 파라미터가 있으면 캐시 무시
|
||||
const bustCache = !!req.query._t;
|
||||
|
||||
const result = await tableManagementService.getColumnList(
|
||||
tableName,
|
||||
parseInt(page as string),
|
||||
parseInt(size as string),
|
||||
companyCode // 🔥 회사 코드 전달
|
||||
companyCode, // 🔥 회사 코드 전달
|
||||
bustCache // 🔥 캐시 버스팅 옵션
|
||||
);
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -54,3 +54,4 @@ export default router;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -50,3 +50,4 @@ export default router;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -66,3 +66,4 @@ export default router;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -54,3 +54,4 @@ export default router;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Router } from "express";
|
||||
import { authenticateToken } from "../middleware/authMiddleware";
|
||||
import { searchEntity } from "../controllers/entitySearchController";
|
||||
import { searchEntity, getEntityOptions } from "../controllers/entitySearchController";
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -12,3 +12,12 @@ router.get("/:tableName", authenticateToken, searchEntity);
|
||||
|
||||
export default router;
|
||||
|
||||
// 엔티티 옵션 라우터 (UnifiedSelect용)
|
||||
export const entityOptionsRouter = Router();
|
||||
|
||||
/**
|
||||
* 엔티티 옵션 조회 API
|
||||
* GET /api/entity/:tableName/options
|
||||
*/
|
||||
entityOptionsRouter.get("/:tableName/options", authenticateToken, getEntityOptions);
|
||||
|
||||
|
||||
@@ -1658,10 +1658,16 @@ export class ScreenManagementService {
|
||||
? inputTypeMap.get(`${tableName}.${columnName}`)
|
||||
: null;
|
||||
|
||||
// 🆕 Unified 컴포넌트는 덮어쓰지 않음 (새로운 컴포넌트 시스템 보호)
|
||||
const savedComponentType = properties?.componentType;
|
||||
const isUnifiedComponent = savedComponentType?.startsWith("unified-");
|
||||
|
||||
const component = {
|
||||
id: layout.component_id,
|
||||
// 🔥 최신 componentType이 있으면 type 덮어쓰기
|
||||
type: latestTypeInfo?.componentType || layout.component_type as any,
|
||||
// 🔥 최신 componentType이 있으면 type 덮어쓰기 (단, Unified 컴포넌트는 제외)
|
||||
type: isUnifiedComponent
|
||||
? layout.component_type as any // Unified는 저장된 값 유지
|
||||
: (latestTypeInfo?.componentType || layout.component_type as any),
|
||||
position: {
|
||||
x: layout.position_x,
|
||||
y: layout.position_y,
|
||||
@@ -1670,8 +1676,8 @@ export class ScreenManagementService {
|
||||
size: { width: layout.width, height: layout.height },
|
||||
parentId: layout.parent_id,
|
||||
...properties,
|
||||
// 🔥 최신 inputType이 있으면 widgetType, componentType 덮어쓰기
|
||||
...(latestTypeInfo && {
|
||||
// 🔥 최신 inputType이 있으면 widgetType, componentType 덮어쓰기 (단, Unified 컴포넌트는 제외)
|
||||
...(!isUnifiedComponent && latestTypeInfo && {
|
||||
widgetType: latestTypeInfo.inputType,
|
||||
inputType: latestTypeInfo.inputType,
|
||||
componentType: latestTypeInfo.componentType,
|
||||
|
||||
@@ -114,7 +114,8 @@ export class TableManagementService {
|
||||
tableName: string,
|
||||
page: number = 1,
|
||||
size: number = 50,
|
||||
companyCode?: string // 🔥 회사 코드 추가
|
||||
companyCode?: string, // 🔥 회사 코드 추가
|
||||
bustCache: boolean = false // 🔥 캐시 버스팅 옵션
|
||||
): Promise<{
|
||||
columns: ColumnTypeInfo[];
|
||||
total: number;
|
||||
@@ -124,7 +125,7 @@ export class TableManagementService {
|
||||
}> {
|
||||
try {
|
||||
logger.info(
|
||||
`컬럼 정보 조회 시작: ${tableName} (page: ${page}, size: ${size}), company: ${companyCode}`
|
||||
`컬럼 정보 조회 시작: ${tableName} (page: ${page}, size: ${size}), company: ${companyCode}, bustCache: ${bustCache}`
|
||||
);
|
||||
|
||||
// 캐시 키 생성 (companyCode 포함)
|
||||
@@ -132,32 +133,37 @@ export class TableManagementService {
|
||||
CacheKeys.TABLE_COLUMNS(tableName, page, size) + `_${companyCode}`;
|
||||
const countCacheKey = CacheKeys.TABLE_COLUMN_COUNT(tableName);
|
||||
|
||||
// 캐시에서 먼저 확인
|
||||
const cachedResult = cache.get<{
|
||||
columns: ColumnTypeInfo[];
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
totalPages: number;
|
||||
}>(cacheKey);
|
||||
if (cachedResult) {
|
||||
logger.info(
|
||||
`컬럼 정보 캐시에서 조회: ${tableName}, ${cachedResult.columns.length}/${cachedResult.total}개`
|
||||
);
|
||||
// 🔥 캐시 버스팅: bustCache가 true면 캐시 무시
|
||||
if (!bustCache) {
|
||||
// 캐시에서 먼저 확인
|
||||
const cachedResult = cache.get<{
|
||||
columns: ColumnTypeInfo[];
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
totalPages: number;
|
||||
}>(cacheKey);
|
||||
if (cachedResult) {
|
||||
logger.info(
|
||||
`컬럼 정보 캐시에서 조회: ${tableName}, ${cachedResult.columns.length}/${cachedResult.total}개`
|
||||
);
|
||||
|
||||
// 디버깅: 캐시된 currency_code 확인
|
||||
const cachedCurrency = cachedResult.columns.find(
|
||||
(col: any) => col.columnName === "currency_code"
|
||||
);
|
||||
if (cachedCurrency) {
|
||||
console.log(`💾 [캐시] currency_code:`, {
|
||||
columnName: cachedCurrency.columnName,
|
||||
inputType: cachedCurrency.inputType,
|
||||
webType: cachedCurrency.webType,
|
||||
});
|
||||
// 디버깅: 캐시된 currency_code 확인
|
||||
const cachedCurrency = cachedResult.columns.find(
|
||||
(col: any) => col.columnName === "currency_code"
|
||||
);
|
||||
if (cachedCurrency) {
|
||||
console.log(`💾 [캐시] currency_code:`, {
|
||||
columnName: cachedCurrency.columnName,
|
||||
inputType: cachedCurrency.inputType,
|
||||
webType: cachedCurrency.webType,
|
||||
});
|
||||
}
|
||||
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
return cachedResult;
|
||||
} else {
|
||||
logger.info(`🔥 캐시 버스팅: ${tableName} 캐시 무시`);
|
||||
}
|
||||
|
||||
// 전체 컬럼 수 조회 (캐시 확인)
|
||||
|
||||
Reference in New Issue
Block a user