Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/v2-unified-renewal
This commit is contained in:
@@ -809,12 +809,6 @@ export async function getTableData(
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 최종 검색 조건 로그
|
||||
logger.info(
|
||||
`🔍 최종 검색 조건 (enhancedSearch):`,
|
||||
JSON.stringify(enhancedSearch)
|
||||
);
|
||||
|
||||
// 데이터 조회
|
||||
const result = await tableManagementService.getTableData(tableName, {
|
||||
page: parseInt(page),
|
||||
@@ -898,10 +892,7 @@ export async function addTableData(
|
||||
const companyCode = req.user?.companyCode;
|
||||
if (companyCode && !data.company_code) {
|
||||
// 테이블에 company_code 컬럼이 있는지 확인
|
||||
const hasCompanyCodeColumn = await tableManagementService.hasColumn(
|
||||
tableName,
|
||||
"company_code"
|
||||
);
|
||||
const hasCompanyCodeColumn = await tableManagementService.hasColumn(tableName, "company_code");
|
||||
if (hasCompanyCodeColumn) {
|
||||
data.company_code = companyCode;
|
||||
logger.info(`멀티테넌시: company_code 자동 추가 - ${companyCode}`);
|
||||
@@ -911,10 +902,7 @@ export async function addTableData(
|
||||
// 🆕 writer 컬럼 자동 추가 (테이블에 writer 컬럼이 있고 값이 없는 경우)
|
||||
const userId = req.user?.userId;
|
||||
if (userId && !data.writer) {
|
||||
const hasWriterColumn = await tableManagementService.hasColumn(
|
||||
tableName,
|
||||
"writer"
|
||||
);
|
||||
const hasWriterColumn = await tableManagementService.hasColumn(tableName, "writer");
|
||||
if (hasWriterColumn) {
|
||||
data.writer = userId;
|
||||
logger.info(`writer 자동 추가 - ${userId}`);
|
||||
@@ -922,25 +910,13 @@ export async function addTableData(
|
||||
}
|
||||
|
||||
// 데이터 추가
|
||||
const result = await tableManagementService.addTableData(tableName, data);
|
||||
await tableManagementService.addTableData(tableName, data);
|
||||
|
||||
logger.info(`테이블 데이터 추가 완료: ${tableName}`);
|
||||
|
||||
// 무시된 컬럼이 있으면 경고 정보 포함
|
||||
const response: ApiResponse<{
|
||||
skippedColumns?: string[];
|
||||
savedColumns?: string[];
|
||||
}> = {
|
||||
const response: ApiResponse<null> = {
|
||||
success: true,
|
||||
message:
|
||||
result.skippedColumns.length > 0
|
||||
? `테이블 데이터를 추가했습니다. (무시된 컬럼 ${result.skippedColumns.length}개: ${result.skippedColumns.join(", ")})`
|
||||
: "테이블 데이터를 성공적으로 추가했습니다.",
|
||||
data: {
|
||||
skippedColumns:
|
||||
result.skippedColumns.length > 0 ? result.skippedColumns : undefined,
|
||||
savedColumns: result.savedColumns,
|
||||
},
|
||||
message: "테이블 데이터를 성공적으로 추가했습니다.",
|
||||
};
|
||||
|
||||
res.status(201).json(response);
|
||||
@@ -1668,10 +1644,10 @@ export async function toggleLogTable(
|
||||
|
||||
/**
|
||||
* 메뉴의 상위 메뉴들이 설정한 모든 카테고리 타입 컬럼 조회 (계층 구조 상속)
|
||||
*
|
||||
*
|
||||
* @route GET /api/table-management/menu/:menuObjid/category-columns
|
||||
* @description 현재 메뉴와 상위 메뉴들에서 설정한 category_column_mapping의 모든 카테고리 컬럼 조회
|
||||
*
|
||||
*
|
||||
* 예시:
|
||||
* - 2레벨 메뉴 "고객사관리"에서 discount_type, rounding_type 설정
|
||||
* - 3레벨 메뉴 "고객등록", "고객조회" 등에서도 동일하게 보임 (상속)
|
||||
@@ -1684,10 +1660,7 @@ export async function getCategoryColumnsByMenu(
|
||||
const { menuObjid } = req.params;
|
||||
const companyCode = req.user?.companyCode;
|
||||
|
||||
logger.info("📥 메뉴별 카테고리 컬럼 조회 요청", {
|
||||
menuObjid,
|
||||
companyCode,
|
||||
});
|
||||
logger.info("📥 메뉴별 카테고리 컬럼 조회 요청", { menuObjid, companyCode });
|
||||
|
||||
if (!menuObjid) {
|
||||
res.status(400).json({
|
||||
@@ -1713,11 +1686,8 @@ export async function getCategoryColumnsByMenu(
|
||||
|
||||
if (mappingTableExists) {
|
||||
// 🆕 category_column_mapping을 사용한 계층 구조 기반 조회
|
||||
logger.info(
|
||||
"🔍 category_column_mapping 기반 카테고리 컬럼 조회 (계층 구조 상속)",
|
||||
{ menuObjid, companyCode }
|
||||
);
|
||||
|
||||
logger.info("🔍 category_column_mapping 기반 카테고리 컬럼 조회 (계층 구조 상속)", { menuObjid, companyCode });
|
||||
|
||||
// 현재 메뉴와 모든 상위 메뉴의 objid 조회 (재귀)
|
||||
const ancestorMenuQuery = `
|
||||
WITH RECURSIVE menu_hierarchy AS (
|
||||
@@ -1739,21 +1709,17 @@ export async function getCategoryColumnsByMenu(
|
||||
ARRAY_AGG(menu_name_kor) as menu_names
|
||||
FROM menu_hierarchy
|
||||
`;
|
||||
|
||||
const ancestorMenuResult = await pool.query(ancestorMenuQuery, [
|
||||
parseInt(menuObjid),
|
||||
]);
|
||||
const ancestorMenuObjids = ancestorMenuResult.rows[0]?.menu_objids || [
|
||||
parseInt(menuObjid),
|
||||
];
|
||||
|
||||
const ancestorMenuResult = await pool.query(ancestorMenuQuery, [parseInt(menuObjid)]);
|
||||
const ancestorMenuObjids = ancestorMenuResult.rows[0]?.menu_objids || [parseInt(menuObjid)];
|
||||
const ancestorMenuNames = ancestorMenuResult.rows[0]?.menu_names || [];
|
||||
|
||||
logger.info("✅ 상위 메뉴 계층 조회 완료", {
|
||||
ancestorMenuObjids,
|
||||
|
||||
logger.info("✅ 상위 메뉴 계층 조회 완료", {
|
||||
ancestorMenuObjids,
|
||||
ancestorMenuNames,
|
||||
hierarchyDepth: ancestorMenuObjids.length,
|
||||
hierarchyDepth: ancestorMenuObjids.length
|
||||
});
|
||||
|
||||
|
||||
// 상위 메뉴들에 설정된 모든 카테고리 컬럼 조회 (테이블 필터링 제거)
|
||||
const columnsQuery = `
|
||||
SELECT DISTINCT
|
||||
@@ -1783,31 +1749,20 @@ export async function getCategoryColumnsByMenu(
|
||||
AND ttc.input_type = 'category'
|
||||
ORDER BY ttc.table_name, ccm.logical_column_name
|
||||
`;
|
||||
|
||||
columnsResult = await pool.query(columnsQuery, [
|
||||
companyCode,
|
||||
ancestorMenuObjids,
|
||||
]);
|
||||
logger.info(
|
||||
"✅ category_column_mapping 기반 조회 완료 (계층 구조 상속)",
|
||||
{
|
||||
rowCount: columnsResult.rows.length,
|
||||
columns: columnsResult.rows.map(
|
||||
(r: any) => `${r.tableName}.${r.columnName}`
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
columnsResult = await pool.query(columnsQuery, [companyCode, ancestorMenuObjids]);
|
||||
logger.info("✅ category_column_mapping 기반 조회 완료 (계층 구조 상속)", {
|
||||
rowCount: columnsResult.rows.length,
|
||||
columns: columnsResult.rows.map((r: any) => `${r.tableName}.${r.columnName}`)
|
||||
});
|
||||
} else {
|
||||
// 🔄 레거시 방식: 형제 메뉴들의 테이블에서 모든 카테고리 컬럼 조회
|
||||
logger.info("🔍 레거시 방식: 형제 메뉴 테이블 기반 카테고리 컬럼 조회", {
|
||||
menuObjid,
|
||||
companyCode,
|
||||
});
|
||||
|
||||
logger.info("🔍 레거시 방식: 형제 메뉴 테이블 기반 카테고리 컬럼 조회", { menuObjid, companyCode });
|
||||
|
||||
// 형제 메뉴 조회
|
||||
const { getSiblingMenuObjids } = await import("../services/menuService");
|
||||
const siblingObjids = await getSiblingMenuObjids(parseInt(menuObjid));
|
||||
|
||||
|
||||
// 형제 메뉴들이 사용하는 테이블 조회
|
||||
const tablesQuery = `
|
||||
SELECT DISTINCT sd.table_name
|
||||
@@ -1817,17 +1772,11 @@ export async function getCategoryColumnsByMenu(
|
||||
AND sma.company_code = $2
|
||||
AND sd.table_name IS NOT NULL
|
||||
`;
|
||||
|
||||
const tablesResult = await pool.query(tablesQuery, [
|
||||
siblingObjids,
|
||||
companyCode,
|
||||
]);
|
||||
|
||||
const tablesResult = await pool.query(tablesQuery, [siblingObjids, companyCode]);
|
||||
const tableNames = tablesResult.rows.map((row: any) => row.table_name);
|
||||
|
||||
logger.info("✅ 형제 메뉴 테이블 조회 완료", {
|
||||
tableNames,
|
||||
count: tableNames.length,
|
||||
});
|
||||
|
||||
logger.info("✅ 형제 메뉴 테이블 조회 완료", { tableNames, count: tableNames.length });
|
||||
|
||||
if (tableNames.length === 0) {
|
||||
res.json({
|
||||
@@ -1837,7 +1786,7 @@ export async function getCategoryColumnsByMenu(
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const columnsQuery = `
|
||||
SELECT
|
||||
ttc.table_name AS "tableName",
|
||||
@@ -1862,15 +1811,13 @@ export async function getCategoryColumnsByMenu(
|
||||
AND ttc.input_type = 'category'
|
||||
ORDER BY ttc.table_name, ttc.column_name
|
||||
`;
|
||||
|
||||
|
||||
columnsResult = await pool.query(columnsQuery, [tableNames, companyCode]);
|
||||
logger.info("✅ 레거시 방식 조회 완료", {
|
||||
rowCount: columnsResult.rows.length,
|
||||
});
|
||||
logger.info("✅ 레거시 방식 조회 완료", { rowCount: columnsResult.rows.length });
|
||||
}
|
||||
|
||||
logger.info("✅ 카테고리 컬럼 조회 완료", {
|
||||
columnCount: columnsResult.rows.length,
|
||||
|
||||
logger.info("✅ 카테고리 컬럼 조회 완료", {
|
||||
columnCount: columnsResult.rows.length
|
||||
});
|
||||
|
||||
res.json({
|
||||
@@ -1895,9 +1842,9 @@ export async function getCategoryColumnsByMenu(
|
||||
|
||||
/**
|
||||
* 범용 다중 테이블 저장 API
|
||||
*
|
||||
*
|
||||
* 메인 테이블과 서브 테이블(들)에 트랜잭션으로 데이터를 저장합니다.
|
||||
*
|
||||
*
|
||||
* 요청 본문:
|
||||
* {
|
||||
* mainTable: { tableName: string, primaryKeyColumn: string },
|
||||
@@ -1967,29 +1914,23 @@ export async function multiTableSave(
|
||||
}
|
||||
|
||||
let mainResult: any;
|
||||
|
||||
|
||||
if (isUpdate && pkValue) {
|
||||
// UPDATE
|
||||
const updateColumns = Object.keys(mainData)
|
||||
.filter((col) => col !== pkColumn)
|
||||
.filter(col => col !== pkColumn)
|
||||
.map((col, idx) => `"${col}" = $${idx + 1}`)
|
||||
.join(", ");
|
||||
const updateValues = Object.keys(mainData)
|
||||
.filter((col) => col !== pkColumn)
|
||||
.map((col) => mainData[col]);
|
||||
|
||||
.filter(col => col !== pkColumn)
|
||||
.map(col => mainData[col]);
|
||||
|
||||
// updated_at 컬럼 존재 여부 확인
|
||||
const hasUpdatedAt = await client.query(
|
||||
`
|
||||
const hasUpdatedAt = await client.query(`
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = $1 AND column_name = 'updated_at'
|
||||
`,
|
||||
[mainTableName]
|
||||
);
|
||||
const updatedAtClause =
|
||||
hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0
|
||||
? ", updated_at = NOW()"
|
||||
: "";
|
||||
`, [mainTableName]);
|
||||
const updatedAtClause = hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0 ? ", updated_at = NOW()" : "";
|
||||
|
||||
const updateQuery = `
|
||||
UPDATE "${mainTableName}"
|
||||
@@ -1998,43 +1939,29 @@ export async function multiTableSave(
|
||||
${companyCode !== "*" ? `AND company_code = $${updateValues.length + 2}` : ""}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const updateParams =
|
||||
companyCode !== "*"
|
||||
? [...updateValues, pkValue, companyCode]
|
||||
: [...updateValues, pkValue];
|
||||
|
||||
logger.info("메인 테이블 UPDATE:", {
|
||||
query: updateQuery,
|
||||
paramsCount: updateParams.length,
|
||||
});
|
||||
|
||||
const updateParams = companyCode !== "*"
|
||||
? [...updateValues, pkValue, companyCode]
|
||||
: [...updateValues, pkValue];
|
||||
|
||||
logger.info("메인 테이블 UPDATE:", { query: updateQuery, paramsCount: updateParams.length });
|
||||
mainResult = await client.query(updateQuery, updateParams);
|
||||
} else {
|
||||
// INSERT
|
||||
const columns = Object.keys(mainData)
|
||||
.map((col) => `"${col}"`)
|
||||
.join(", ");
|
||||
const placeholders = Object.keys(mainData)
|
||||
.map((_, idx) => `$${idx + 1}`)
|
||||
.join(", ");
|
||||
const columns = Object.keys(mainData).map(col => `"${col}"`).join(", ");
|
||||
const placeholders = Object.keys(mainData).map((_, idx) => `$${idx + 1}`).join(", ");
|
||||
const values = Object.values(mainData);
|
||||
|
||||
// updated_at 컬럼 존재 여부 확인
|
||||
const hasUpdatedAt = await client.query(
|
||||
`
|
||||
const hasUpdatedAt = await client.query(`
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = $1 AND column_name = 'updated_at'
|
||||
`,
|
||||
[mainTableName]
|
||||
);
|
||||
const updatedAtClause =
|
||||
hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0
|
||||
? ", updated_at = NOW()"
|
||||
: "";
|
||||
`, [mainTableName]);
|
||||
const updatedAtClause = hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0 ? ", updated_at = NOW()" : "";
|
||||
|
||||
const updateSetClause = Object.keys(mainData)
|
||||
.filter((col) => col !== pkColumn)
|
||||
.map((col) => `"${col}" = EXCLUDED."${col}"`)
|
||||
.filter(col => col !== pkColumn)
|
||||
.map(col => `"${col}" = EXCLUDED."${col}"`)
|
||||
.join(", ");
|
||||
|
||||
const insertQuery = `
|
||||
@@ -2045,10 +1972,7 @@ export async function multiTableSave(
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
logger.info("메인 테이블 INSERT/UPSERT:", {
|
||||
query: insertQuery,
|
||||
paramsCount: values.length,
|
||||
});
|
||||
logger.info("메인 테이블 INSERT/UPSERT:", { query: insertQuery, paramsCount: values.length });
|
||||
mainResult = await client.query(insertQuery, values);
|
||||
}
|
||||
|
||||
@@ -2067,15 +1991,12 @@ export async function multiTableSave(
|
||||
const { tableName, linkColumn, items, options } = subTableConfig;
|
||||
|
||||
// saveMainAsFirst가 활성화된 경우, items가 비어있어도 메인 데이터를 서브 테이블에 저장해야 함
|
||||
const hasSaveMainAsFirst =
|
||||
options?.saveMainAsFirst &&
|
||||
options?.mainFieldMappings &&
|
||||
options.mainFieldMappings.length > 0;
|
||||
|
||||
const hasSaveMainAsFirst = options?.saveMainAsFirst &&
|
||||
options?.mainFieldMappings &&
|
||||
options.mainFieldMappings.length > 0;
|
||||
|
||||
if (!tableName || (!items?.length && !hasSaveMainAsFirst)) {
|
||||
logger.info(
|
||||
`서브 테이블 ${tableName} 스킵: 데이터 없음 (saveMainAsFirst: ${hasSaveMainAsFirst})`
|
||||
);
|
||||
logger.info(`서브 테이블 ${tableName} 스킵: 데이터 없음 (saveMainAsFirst: ${hasSaveMainAsFirst})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2088,20 +2009,15 @@ export async function multiTableSave(
|
||||
|
||||
// 기존 데이터 삭제 옵션
|
||||
if (options?.deleteExistingBefore && linkColumn?.subColumn) {
|
||||
const deleteQuery =
|
||||
options?.deleteOnlySubItems && options?.mainMarkerColumn
|
||||
? `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1 AND "${options.mainMarkerColumn}" = $2`
|
||||
: `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1`;
|
||||
const deleteQuery = options?.deleteOnlySubItems && options?.mainMarkerColumn
|
||||
? `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1 AND "${options.mainMarkerColumn}" = $2`
|
||||
: `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1`;
|
||||
|
||||
const deleteParams = options?.deleteOnlySubItems && options?.mainMarkerColumn
|
||||
? [savedPkValue, options.subMarkerValue ?? false]
|
||||
: [savedPkValue];
|
||||
|
||||
const deleteParams =
|
||||
options?.deleteOnlySubItems && options?.mainMarkerColumn
|
||||
? [savedPkValue, options.subMarkerValue ?? false]
|
||||
: [savedPkValue];
|
||||
|
||||
logger.info(`서브 테이블 ${tableName} 기존 데이터 삭제:`, {
|
||||
deleteQuery,
|
||||
deleteParams,
|
||||
});
|
||||
logger.info(`서브 테이블 ${tableName} 기존 데이터 삭제:`, { deleteQuery, deleteParams });
|
||||
await client.query(deleteQuery, deleteParams);
|
||||
}
|
||||
|
||||
@@ -2114,12 +2030,7 @@ export async function multiTableSave(
|
||||
linkColumn,
|
||||
mainDataKeys: Object.keys(mainData),
|
||||
});
|
||||
if (
|
||||
options?.saveMainAsFirst &&
|
||||
options?.mainFieldMappings &&
|
||||
options.mainFieldMappings.length > 0 &&
|
||||
linkColumn?.subColumn
|
||||
) {
|
||||
if (options?.saveMainAsFirst && options?.mainFieldMappings && options.mainFieldMappings.length > 0 && linkColumn?.subColumn) {
|
||||
const mainSubItem: Record<string, any> = {
|
||||
[linkColumn.subColumn]: savedPkValue,
|
||||
};
|
||||
@@ -2133,8 +2044,7 @@ export async function multiTableSave(
|
||||
|
||||
// 메인 마커 설정
|
||||
if (options.mainMarkerColumn) {
|
||||
mainSubItem[options.mainMarkerColumn] =
|
||||
options.mainMarkerValue ?? true;
|
||||
mainSubItem[options.mainMarkerColumn] = options.mainMarkerValue ?? true;
|
||||
}
|
||||
|
||||
// company_code 추가
|
||||
@@ -2157,30 +2067,20 @@ export async function multiTableSave(
|
||||
if (companyCode !== "*") {
|
||||
checkParams.push(companyCode);
|
||||
}
|
||||
|
||||
|
||||
const existingResult = await client.query(checkQuery, checkParams);
|
||||
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
// UPDATE
|
||||
const updateColumns = Object.keys(mainSubItem)
|
||||
.filter(
|
||||
(col) =>
|
||||
col !== linkColumn.subColumn &&
|
||||
col !== options.mainMarkerColumn &&
|
||||
col !== "company_code"
|
||||
)
|
||||
.filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn && col !== "company_code")
|
||||
.map((col, idx) => `"${col}" = $${idx + 1}`)
|
||||
.join(", ");
|
||||
|
||||
|
||||
const updateValues = Object.keys(mainSubItem)
|
||||
.filter(
|
||||
(col) =>
|
||||
col !== linkColumn.subColumn &&
|
||||
col !== options.mainMarkerColumn &&
|
||||
col !== "company_code"
|
||||
)
|
||||
.map((col) => mainSubItem[col]);
|
||||
|
||||
.filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn && col !== "company_code")
|
||||
.map(col => mainSubItem[col]);
|
||||
|
||||
if (updateColumns) {
|
||||
const updateQuery = `
|
||||
UPDATE "${tableName}"
|
||||
@@ -2199,26 +2099,14 @@ export async function multiTableSave(
|
||||
}
|
||||
|
||||
const updateResult = await client.query(updateQuery, updateParams);
|
||||
subTableResults.push({
|
||||
tableName,
|
||||
type: "main",
|
||||
data: updateResult.rows[0],
|
||||
});
|
||||
subTableResults.push({ tableName, type: "main", data: updateResult.rows[0] });
|
||||
} else {
|
||||
subTableResults.push({
|
||||
tableName,
|
||||
type: "main",
|
||||
data: existingResult.rows[0],
|
||||
});
|
||||
subTableResults.push({ tableName, type: "main", data: existingResult.rows[0] });
|
||||
}
|
||||
} else {
|
||||
// INSERT
|
||||
const mainSubColumns = Object.keys(mainSubItem)
|
||||
.map((col) => `"${col}"`)
|
||||
.join(", ");
|
||||
const mainSubPlaceholders = Object.keys(mainSubItem)
|
||||
.map((_, idx) => `$${idx + 1}`)
|
||||
.join(", ");
|
||||
const mainSubColumns = Object.keys(mainSubItem).map(col => `"${col}"`).join(", ");
|
||||
const mainSubPlaceholders = Object.keys(mainSubItem).map((_, idx) => `$${idx + 1}`).join(", ");
|
||||
const mainSubValues = Object.values(mainSubItem);
|
||||
|
||||
const insertQuery = `
|
||||
@@ -2228,11 +2116,7 @@ export async function multiTableSave(
|
||||
`;
|
||||
|
||||
const insertResult = await client.query(insertQuery, mainSubValues);
|
||||
subTableResults.push({
|
||||
tableName,
|
||||
type: "main",
|
||||
data: insertResult.rows[0],
|
||||
});
|
||||
subTableResults.push({ tableName, type: "main", data: insertResult.rows[0] });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2248,12 +2132,8 @@ export async function multiTableSave(
|
||||
item.company_code = companyCode;
|
||||
}
|
||||
|
||||
const subColumns = Object.keys(item)
|
||||
.map((col) => `"${col}"`)
|
||||
.join(", ");
|
||||
const subPlaceholders = Object.keys(item)
|
||||
.map((_, idx) => `$${idx + 1}`)
|
||||
.join(", ");
|
||||
const subColumns = Object.keys(item).map(col => `"${col}"`).join(", ");
|
||||
const subPlaceholders = Object.keys(item).map((_, idx) => `$${idx + 1}`).join(", ");
|
||||
const subValues = Object.values(item);
|
||||
|
||||
const subInsertQuery = `
|
||||
@@ -2262,16 +2142,9 @@ export async function multiTableSave(
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
logger.info(`서브 테이블 ${tableName} 아이템 저장:`, {
|
||||
subInsertQuery,
|
||||
subValuesCount: subValues.length,
|
||||
});
|
||||
logger.info(`서브 테이블 ${tableName} 아이템 저장:`, { subInsertQuery, subValuesCount: subValues.length });
|
||||
const subResult = await client.query(subInsertQuery, subValues);
|
||||
subTableResults.push({
|
||||
tableName,
|
||||
type: "sub",
|
||||
data: subResult.rows[0],
|
||||
});
|
||||
subTableResults.push({ tableName, type: "sub", data: subResult.rows[0] });
|
||||
}
|
||||
|
||||
logger.info(`서브 테이블 ${tableName} 저장 완료`);
|
||||
@@ -2312,11 +2185,8 @@ export async function multiTableSave(
|
||||
}
|
||||
|
||||
/**
|
||||
* 두 테이블 간의 엔티티 관계 자동 감지
|
||||
* GET /api/table-management/tables/entity-relations?leftTable=xxx&rightTable=yyy
|
||||
*
|
||||
* column_labels에서 정의된 엔티티/카테고리 타입 설정을 기반으로
|
||||
* 두 테이블 간의 외래키 관계를 자동으로 감지합니다.
|
||||
* 두 테이블 간 엔티티 관계 조회
|
||||
* column_labels의 entity/category 타입 설정을 기반으로 두 테이블 간의 관계를 조회
|
||||
*/
|
||||
export async function getTableEntityRelations(
|
||||
req: AuthenticatedRequest,
|
||||
@@ -2325,55 +2195,94 @@ export async function getTableEntityRelations(
|
||||
try {
|
||||
const { leftTable, rightTable } = req.query;
|
||||
|
||||
logger.info(
|
||||
`=== 테이블 엔티티 관계 조회 시작: ${leftTable} <-> ${rightTable} ===`
|
||||
);
|
||||
|
||||
if (!leftTable || !rightTable) {
|
||||
const response: ApiResponse<null> = {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "leftTable과 rightTable 파라미터가 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_PARAMETERS",
|
||||
details: "leftTable과 rightTable 쿼리 파라미터가 필요합니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const tableManagementService = new TableManagementService();
|
||||
const relations = await tableManagementService.detectTableEntityRelations(
|
||||
String(leftTable),
|
||||
String(rightTable)
|
||||
);
|
||||
logger.info("=== 테이블 엔티티 관계 조회 ===", { leftTable, rightTable });
|
||||
|
||||
logger.info(`테이블 엔티티 관계 조회 완료: ${relations.length}개 발견`);
|
||||
// 두 테이블의 컬럼 라벨 정보 조회
|
||||
const columnLabelsQuery = `
|
||||
SELECT
|
||||
table_name,
|
||||
column_name,
|
||||
column_label,
|
||||
web_type,
|
||||
detail_settings
|
||||
FROM column_labels
|
||||
WHERE table_name IN ($1, $2)
|
||||
AND web_type IN ('entity', 'category')
|
||||
`;
|
||||
|
||||
const response: ApiResponse<any> = {
|
||||
const result = await query(columnLabelsQuery, [leftTable, rightTable]);
|
||||
|
||||
// 관계 분석
|
||||
const relations: Array<{
|
||||
fromTable: string;
|
||||
fromColumn: string;
|
||||
toTable: string;
|
||||
toColumn: string;
|
||||
relationType: string;
|
||||
}> = [];
|
||||
|
||||
for (const row of result) {
|
||||
try {
|
||||
const detailSettings = typeof row.detail_settings === "string"
|
||||
? JSON.parse(row.detail_settings)
|
||||
: row.detail_settings;
|
||||
|
||||
if (detailSettings && detailSettings.referenceTable) {
|
||||
const refTable = detailSettings.referenceTable;
|
||||
const refColumn = detailSettings.referenceColumn || "id";
|
||||
|
||||
// leftTable과 rightTable 간의 관계인지 확인
|
||||
if (
|
||||
(row.table_name === leftTable && refTable === rightTable) ||
|
||||
(row.table_name === rightTable && refTable === leftTable)
|
||||
) {
|
||||
relations.push({
|
||||
fromTable: row.table_name,
|
||||
fromColumn: row.column_name,
|
||||
toTable: refTable,
|
||||
toColumn: refColumn,
|
||||
relationType: row.web_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
logger.warn("detail_settings 파싱 오류:", {
|
||||
table: row.table_name,
|
||||
column: row.column_name,
|
||||
error: parseError
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("테이블 엔티티 관계 조회 완료", {
|
||||
leftTable,
|
||||
rightTable,
|
||||
relationsCount: relations.length
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${relations.length}개의 엔티티 관계를 발견했습니다.`,
|
||||
data: {
|
||||
leftTable: String(leftTable),
|
||||
rightTable: String(rightTable),
|
||||
leftTable,
|
||||
rightTable,
|
||||
relations,
|
||||
},
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("테이블 엔티티 관계 조회 중 오류 발생:", error);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("테이블 엔티티 관계 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "테이블 엔티티 관계 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "ENTITY_RELATIONS_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
message: "테이블 엔티티 관계 조회에 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user