753 lines
21 KiB
TypeScript
753 lines
21 KiB
TypeScript
/**
|
|
* 다단계 계층 (Hierarchy) 컨트롤러
|
|
* 국가 > 도시 > 구/군 같은 다단계 연쇄 드롭다운 관리
|
|
*/
|
|
|
|
import { Request, Response } from "express";
|
|
import { query, queryOne } from "../database/db";
|
|
import logger from "../utils/logger";
|
|
|
|
// =====================================================
|
|
// 계층 그룹 CRUD
|
|
// =====================================================
|
|
|
|
/**
|
|
* 계층 그룹 목록 조회
|
|
*/
|
|
export const getHierarchyGroups = async (req: Request, res: Response) => {
|
|
try {
|
|
const companyCode = req.user?.companyCode || "*";
|
|
const { isActive, hierarchyType } = req.query;
|
|
|
|
let sql = `
|
|
SELECT g.*,
|
|
(SELECT COUNT(*) FROM cascading_hierarchy_level l WHERE l.group_code = g.group_code AND l.company_code = g.company_code) as level_count
|
|
FROM cascading_hierarchy_group g
|
|
WHERE 1=1
|
|
`;
|
|
const params: any[] = [];
|
|
let paramIndex = 1;
|
|
|
|
if (companyCode !== "*") {
|
|
sql += ` AND g.company_code = $${paramIndex++}`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
if (isActive) {
|
|
sql += ` AND g.is_active = $${paramIndex++}`;
|
|
params.push(isActive);
|
|
}
|
|
|
|
if (hierarchyType) {
|
|
sql += ` AND g.hierarchy_type = $${paramIndex++}`;
|
|
params.push(hierarchyType);
|
|
}
|
|
|
|
sql += ` ORDER BY g.group_name`;
|
|
|
|
const result = await query(sql, params);
|
|
|
|
logger.info("계층 그룹 목록 조회", { count: result.length, companyCode });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 그룹 목록 조회 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "계층 그룹 목록 조회에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 계층 그룹 상세 조회 (레벨 포함)
|
|
*/
|
|
export const getHierarchyGroupDetail = async (req: Request, res: Response) => {
|
|
try {
|
|
const { groupCode } = req.params;
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
// 그룹 조회
|
|
let groupSql = `SELECT * FROM cascading_hierarchy_group WHERE group_code = $1`;
|
|
const groupParams: any[] = [groupCode];
|
|
|
|
if (companyCode !== "*") {
|
|
groupSql += ` AND company_code = $2`;
|
|
groupParams.push(companyCode);
|
|
}
|
|
|
|
const group = await queryOne(groupSql, groupParams);
|
|
|
|
if (!group) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "계층 그룹을 찾을 수 없습니다.",
|
|
});
|
|
}
|
|
|
|
// 레벨 조회
|
|
let levelSql = `SELECT * FROM cascading_hierarchy_level WHERE group_code = $1`;
|
|
const levelParams: any[] = [groupCode];
|
|
|
|
if (companyCode !== "*") {
|
|
levelSql += ` AND company_code = $2`;
|
|
levelParams.push(companyCode);
|
|
}
|
|
|
|
levelSql += ` ORDER BY level_order`;
|
|
|
|
const levels = await query(levelSql, levelParams);
|
|
|
|
logger.info("계층 그룹 상세 조회", { groupCode, companyCode });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
...group,
|
|
levels: levels,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 그룹 상세 조회 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "계층 그룹 상세 조회에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 계층 그룹 코드 자동 생성 함수
|
|
*/
|
|
const generateHierarchyGroupCode = async (companyCode: string): Promise<string> => {
|
|
const prefix = "HG";
|
|
const result = await queryOne(
|
|
`SELECT COUNT(*) as cnt FROM cascading_hierarchy_group WHERE company_code = $1`,
|
|
[companyCode]
|
|
);
|
|
const count = parseInt(result?.cnt || "0", 10) + 1;
|
|
const timestamp = Date.now().toString(36).toUpperCase().slice(-4);
|
|
return `${prefix}_${timestamp}_${count.toString().padStart(3, "0")}`;
|
|
};
|
|
|
|
/**
|
|
* 계층 그룹 생성
|
|
*/
|
|
export const createHierarchyGroup = async (req: Request, res: Response) => {
|
|
try {
|
|
const companyCode = req.user?.companyCode || "*";
|
|
const userId = req.user?.userId || "system";
|
|
const {
|
|
groupName,
|
|
description,
|
|
hierarchyType = "MULTI_TABLE",
|
|
maxLevels,
|
|
isFixedLevels = "Y",
|
|
// Self-reference 설정
|
|
selfRefTable,
|
|
selfRefIdColumn,
|
|
selfRefParentColumn,
|
|
selfRefValueColumn,
|
|
selfRefLabelColumn,
|
|
selfRefLevelColumn,
|
|
selfRefOrderColumn,
|
|
// BOM 설정
|
|
bomTable,
|
|
bomParentColumn,
|
|
bomChildColumn,
|
|
bomItemTable,
|
|
bomItemIdColumn,
|
|
bomItemLabelColumn,
|
|
bomQtyColumn,
|
|
bomLevelColumn,
|
|
// 메시지
|
|
emptyMessage,
|
|
noOptionsMessage,
|
|
loadingMessage,
|
|
// 레벨 (MULTI_TABLE 타입인 경우)
|
|
levels = [],
|
|
} = req.body;
|
|
|
|
// 필수 필드 검증
|
|
if (!groupName || !hierarchyType) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "필수 필드가 누락되었습니다. (groupName, hierarchyType)",
|
|
});
|
|
}
|
|
|
|
// 그룹 코드 자동 생성
|
|
const groupCode = await generateHierarchyGroupCode(companyCode);
|
|
|
|
// 그룹 생성
|
|
const insertGroupSql = `
|
|
INSERT INTO cascading_hierarchy_group (
|
|
group_code, group_name, description, hierarchy_type,
|
|
max_levels, is_fixed_levels,
|
|
self_ref_table, self_ref_id_column, self_ref_parent_column,
|
|
self_ref_value_column, self_ref_label_column, self_ref_level_column, self_ref_order_column,
|
|
bom_table, bom_parent_column, bom_child_column,
|
|
bom_item_table, bom_item_id_column, bom_item_label_column, bom_qty_column, bom_level_column,
|
|
empty_message, no_options_message, loading_message,
|
|
company_code, is_active, created_by, created_date
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, 'Y', $26, CURRENT_TIMESTAMP)
|
|
RETURNING *
|
|
`;
|
|
|
|
const group = await queryOne(insertGroupSql, [
|
|
groupCode,
|
|
groupName,
|
|
description || null,
|
|
hierarchyType,
|
|
maxLevels || null,
|
|
isFixedLevels,
|
|
selfRefTable || null,
|
|
selfRefIdColumn || null,
|
|
selfRefParentColumn || null,
|
|
selfRefValueColumn || null,
|
|
selfRefLabelColumn || null,
|
|
selfRefLevelColumn || null,
|
|
selfRefOrderColumn || null,
|
|
bomTable || null,
|
|
bomParentColumn || null,
|
|
bomChildColumn || null,
|
|
bomItemTable || null,
|
|
bomItemIdColumn || null,
|
|
bomItemLabelColumn || null,
|
|
bomQtyColumn || null,
|
|
bomLevelColumn || null,
|
|
emptyMessage || "선택해주세요",
|
|
noOptionsMessage || "옵션이 없습니다",
|
|
loadingMessage || "로딩 중...",
|
|
companyCode,
|
|
userId,
|
|
]);
|
|
|
|
// 레벨 생성 (MULTI_TABLE 타입인 경우)
|
|
if (hierarchyType === "MULTI_TABLE" && levels.length > 0) {
|
|
for (const level of levels) {
|
|
await query(
|
|
`INSERT INTO cascading_hierarchy_level (
|
|
group_code, company_code, level_order, level_name, level_code,
|
|
table_name, value_column, label_column, parent_key_column,
|
|
filter_column, filter_value, order_column, order_direction,
|
|
placeholder, is_required, is_searchable, is_active, created_date
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, 'Y', CURRENT_TIMESTAMP)`,
|
|
[
|
|
groupCode,
|
|
companyCode,
|
|
level.levelOrder,
|
|
level.levelName,
|
|
level.levelCode || null,
|
|
level.tableName,
|
|
level.valueColumn,
|
|
level.labelColumn,
|
|
level.parentKeyColumn || null,
|
|
level.filterColumn || null,
|
|
level.filterValue || null,
|
|
level.orderColumn || null,
|
|
level.orderDirection || "ASC",
|
|
level.placeholder || `${level.levelName} 선택`,
|
|
level.isRequired || "Y",
|
|
level.isSearchable || "N",
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
logger.info("계층 그룹 생성", { groupCode, hierarchyType, companyCode });
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: "계층 그룹이 생성되었습니다.",
|
|
data: group,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 그룹 생성 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "계층 그룹 생성에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 계층 그룹 수정
|
|
*/
|
|
export const updateHierarchyGroup = async (req: Request, res: Response) => {
|
|
try {
|
|
const { groupCode } = req.params;
|
|
const companyCode = req.user?.companyCode || "*";
|
|
const userId = req.user?.userId || "system";
|
|
const {
|
|
groupName,
|
|
description,
|
|
maxLevels,
|
|
isFixedLevels,
|
|
emptyMessage,
|
|
noOptionsMessage,
|
|
loadingMessage,
|
|
isActive,
|
|
} = req.body;
|
|
|
|
// 기존 그룹 확인
|
|
let checkSql = `SELECT * FROM cascading_hierarchy_group WHERE group_code = $1`;
|
|
const checkParams: any[] = [groupCode];
|
|
|
|
if (companyCode !== "*") {
|
|
checkSql += ` AND company_code = $2`;
|
|
checkParams.push(companyCode);
|
|
}
|
|
|
|
const existing = await queryOne(checkSql, checkParams);
|
|
|
|
if (!existing) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "계층 그룹을 찾을 수 없습니다.",
|
|
});
|
|
}
|
|
|
|
const updateSql = `
|
|
UPDATE cascading_hierarchy_group SET
|
|
group_name = COALESCE($1, group_name),
|
|
description = COALESCE($2, description),
|
|
max_levels = COALESCE($3, max_levels),
|
|
is_fixed_levels = COALESCE($4, is_fixed_levels),
|
|
empty_message = COALESCE($5, empty_message),
|
|
no_options_message = COALESCE($6, no_options_message),
|
|
loading_message = COALESCE($7, loading_message),
|
|
is_active = COALESCE($8, is_active),
|
|
updated_by = $9,
|
|
updated_date = CURRENT_TIMESTAMP
|
|
WHERE group_code = $10 AND company_code = $11
|
|
RETURNING *
|
|
`;
|
|
|
|
const result = await queryOne(updateSql, [
|
|
groupName,
|
|
description,
|
|
maxLevels,
|
|
isFixedLevels,
|
|
emptyMessage,
|
|
noOptionsMessage,
|
|
loadingMessage,
|
|
isActive,
|
|
userId,
|
|
groupCode,
|
|
existing.company_code,
|
|
]);
|
|
|
|
logger.info("계층 그룹 수정", { groupCode, companyCode });
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "계층 그룹이 수정되었습니다.",
|
|
data: result,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 그룹 수정 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "계층 그룹 수정에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 계층 그룹 삭제
|
|
*/
|
|
export const deleteHierarchyGroup = async (req: Request, res: Response) => {
|
|
try {
|
|
const { groupCode } = req.params;
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
// 레벨 먼저 삭제
|
|
let deleteLevelsSql = `DELETE FROM cascading_hierarchy_level WHERE group_code = $1`;
|
|
const levelParams: any[] = [groupCode];
|
|
|
|
if (companyCode !== "*") {
|
|
deleteLevelsSql += ` AND company_code = $2`;
|
|
levelParams.push(companyCode);
|
|
}
|
|
|
|
await query(deleteLevelsSql, levelParams);
|
|
|
|
// 그룹 삭제
|
|
let deleteGroupSql = `DELETE FROM cascading_hierarchy_group WHERE group_code = $1`;
|
|
const groupParams: any[] = [groupCode];
|
|
|
|
if (companyCode !== "*") {
|
|
deleteGroupSql += ` AND company_code = $2`;
|
|
groupParams.push(companyCode);
|
|
}
|
|
|
|
deleteGroupSql += ` RETURNING group_code`;
|
|
|
|
const result = await queryOne(deleteGroupSql, groupParams);
|
|
|
|
if (!result) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "계층 그룹을 찾을 수 없습니다.",
|
|
});
|
|
}
|
|
|
|
logger.info("계층 그룹 삭제", { groupCode, companyCode });
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "계층 그룹이 삭제되었습니다.",
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 그룹 삭제 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "계층 그룹 삭제에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// =====================================================
|
|
// 계층 레벨 관리
|
|
// =====================================================
|
|
|
|
/**
|
|
* 레벨 추가
|
|
*/
|
|
export const addLevel = async (req: Request, res: Response) => {
|
|
try {
|
|
const { groupCode } = req.params;
|
|
const companyCode = req.user?.companyCode || "*";
|
|
const {
|
|
levelOrder,
|
|
levelName,
|
|
levelCode,
|
|
tableName,
|
|
valueColumn,
|
|
labelColumn,
|
|
parentKeyColumn,
|
|
filterColumn,
|
|
filterValue,
|
|
orderColumn,
|
|
orderDirection = "ASC",
|
|
placeholder,
|
|
isRequired = "Y",
|
|
isSearchable = "N",
|
|
} = req.body;
|
|
|
|
// 그룹 존재 확인
|
|
const groupCheck = await queryOne(
|
|
`SELECT * FROM cascading_hierarchy_group WHERE group_code = $1 AND (company_code = $2 OR $2 = '*')`,
|
|
[groupCode, companyCode]
|
|
);
|
|
|
|
if (!groupCheck) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "계층 그룹을 찾을 수 없습니다.",
|
|
});
|
|
}
|
|
|
|
const insertSql = `
|
|
INSERT INTO cascading_hierarchy_level (
|
|
group_code, company_code, level_order, level_name, level_code,
|
|
table_name, value_column, label_column, parent_key_column,
|
|
filter_column, filter_value, order_column, order_direction,
|
|
placeholder, is_required, is_searchable, is_active, created_date
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, 'Y', CURRENT_TIMESTAMP)
|
|
RETURNING *
|
|
`;
|
|
|
|
const result = await queryOne(insertSql, [
|
|
groupCode,
|
|
groupCheck.company_code,
|
|
levelOrder,
|
|
levelName,
|
|
levelCode || null,
|
|
tableName,
|
|
valueColumn,
|
|
labelColumn,
|
|
parentKeyColumn || null,
|
|
filterColumn || null,
|
|
filterValue || null,
|
|
orderColumn || null,
|
|
orderDirection,
|
|
placeholder || `${levelName} 선택`,
|
|
isRequired,
|
|
isSearchable,
|
|
]);
|
|
|
|
logger.info("계층 레벨 추가", { groupCode, levelOrder, levelName });
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: "레벨이 추가되었습니다.",
|
|
data: result,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 레벨 추가 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "레벨 추가에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 레벨 수정
|
|
*/
|
|
export const updateLevel = async (req: Request, res: Response) => {
|
|
try {
|
|
const { levelId } = req.params;
|
|
const companyCode = req.user?.companyCode || "*";
|
|
const {
|
|
levelName,
|
|
tableName,
|
|
valueColumn,
|
|
labelColumn,
|
|
parentKeyColumn,
|
|
filterColumn,
|
|
filterValue,
|
|
orderColumn,
|
|
orderDirection,
|
|
placeholder,
|
|
isRequired,
|
|
isSearchable,
|
|
isActive,
|
|
} = req.body;
|
|
|
|
let checkSql = `SELECT * FROM cascading_hierarchy_level WHERE level_id = $1`;
|
|
const checkParams: any[] = [Number(levelId)];
|
|
|
|
if (companyCode !== "*") {
|
|
checkSql += ` AND company_code = $2`;
|
|
checkParams.push(companyCode);
|
|
}
|
|
|
|
const existing = await queryOne(checkSql, checkParams);
|
|
|
|
if (!existing) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "레벨을 찾을 수 없습니다.",
|
|
});
|
|
}
|
|
|
|
const updateSql = `
|
|
UPDATE cascading_hierarchy_level SET
|
|
level_name = COALESCE($1, level_name),
|
|
table_name = COALESCE($2, table_name),
|
|
value_column = COALESCE($3, value_column),
|
|
label_column = COALESCE($4, label_column),
|
|
parent_key_column = COALESCE($5, parent_key_column),
|
|
filter_column = COALESCE($6, filter_column),
|
|
filter_value = COALESCE($7, filter_value),
|
|
order_column = COALESCE($8, order_column),
|
|
order_direction = COALESCE($9, order_direction),
|
|
placeholder = COALESCE($10, placeholder),
|
|
is_required = COALESCE($11, is_required),
|
|
is_searchable = COALESCE($12, is_searchable),
|
|
is_active = COALESCE($13, is_active),
|
|
updated_date = CURRENT_TIMESTAMP
|
|
WHERE level_id = $14
|
|
RETURNING *
|
|
`;
|
|
|
|
const result = await queryOne(updateSql, [
|
|
levelName,
|
|
tableName,
|
|
valueColumn,
|
|
labelColumn,
|
|
parentKeyColumn,
|
|
filterColumn,
|
|
filterValue,
|
|
orderColumn,
|
|
orderDirection,
|
|
placeholder,
|
|
isRequired,
|
|
isSearchable,
|
|
isActive,
|
|
Number(levelId),
|
|
]);
|
|
|
|
logger.info("계층 레벨 수정", { levelId });
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "레벨이 수정되었습니다.",
|
|
data: result,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 레벨 수정 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "레벨 수정에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 레벨 삭제
|
|
*/
|
|
export const deleteLevel = async (req: Request, res: Response) => {
|
|
try {
|
|
const { levelId } = req.params;
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
let deleteSql = `DELETE FROM cascading_hierarchy_level WHERE level_id = $1`;
|
|
const deleteParams: any[] = [Number(levelId)];
|
|
|
|
if (companyCode !== "*") {
|
|
deleteSql += ` AND company_code = $2`;
|
|
deleteParams.push(companyCode);
|
|
}
|
|
|
|
deleteSql += ` RETURNING level_id`;
|
|
|
|
const result = await queryOne(deleteSql, deleteParams);
|
|
|
|
if (!result) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "레벨을 찾을 수 없습니다.",
|
|
});
|
|
}
|
|
|
|
logger.info("계층 레벨 삭제", { levelId });
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "레벨이 삭제되었습니다.",
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 레벨 삭제 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "레벨 삭제에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// =====================================================
|
|
// 계층 옵션 조회 API (실제 사용)
|
|
// =====================================================
|
|
|
|
/**
|
|
* 특정 레벨의 옵션 조회
|
|
*/
|
|
export const getLevelOptions = async (req: Request, res: Response) => {
|
|
try {
|
|
const { groupCode, levelOrder } = req.params;
|
|
const { parentValue } = req.query;
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
// 레벨 정보 조회
|
|
let levelSql = `
|
|
SELECT l.*, g.hierarchy_type
|
|
FROM cascading_hierarchy_level l
|
|
JOIN cascading_hierarchy_group g ON l.group_code = g.group_code AND l.company_code = g.company_code
|
|
WHERE l.group_code = $1 AND l.level_order = $2 AND l.is_active = 'Y'
|
|
`;
|
|
const levelParams: any[] = [groupCode, Number(levelOrder)];
|
|
|
|
if (companyCode !== "*") {
|
|
levelSql += ` AND l.company_code = $3`;
|
|
levelParams.push(companyCode);
|
|
}
|
|
|
|
const level = await queryOne(levelSql, levelParams);
|
|
|
|
if (!level) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "레벨을 찾을 수 없습니다.",
|
|
});
|
|
}
|
|
|
|
// 옵션 조회
|
|
let optionsSql = `
|
|
SELECT
|
|
${level.value_column} as value,
|
|
${level.label_column} as label
|
|
FROM ${level.table_name}
|
|
WHERE 1=1
|
|
`;
|
|
const optionsParams: any[] = [];
|
|
let optionsParamIndex = 1;
|
|
|
|
// 부모 값 필터 (레벨 2 이상)
|
|
if (level.parent_key_column && parentValue) {
|
|
optionsSql += ` AND ${level.parent_key_column} = $${optionsParamIndex++}`;
|
|
optionsParams.push(parentValue);
|
|
}
|
|
|
|
// 고정 필터
|
|
if (level.filter_column && level.filter_value) {
|
|
optionsSql += ` AND ${level.filter_column} = $${optionsParamIndex++}`;
|
|
optionsParams.push(level.filter_value);
|
|
}
|
|
|
|
// 멀티테넌시 필터
|
|
if (companyCode !== "*") {
|
|
const columnCheck = await queryOne(
|
|
`SELECT column_name FROM information_schema.columns
|
|
WHERE table_name = $1 AND column_name = 'company_code'`,
|
|
[level.table_name]
|
|
);
|
|
|
|
if (columnCheck) {
|
|
optionsSql += ` AND company_code = $${optionsParamIndex++}`;
|
|
optionsParams.push(companyCode);
|
|
}
|
|
}
|
|
|
|
// 정렬
|
|
if (level.order_column) {
|
|
optionsSql += ` ORDER BY ${level.order_column} ${level.order_direction || "ASC"}`;
|
|
} else {
|
|
optionsSql += ` ORDER BY ${level.label_column}`;
|
|
}
|
|
|
|
const optionsResult = await query(optionsSql, optionsParams);
|
|
|
|
logger.info("계층 레벨 옵션 조회", {
|
|
groupCode,
|
|
levelOrder,
|
|
parentValue,
|
|
optionCount: optionsResult.length,
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: optionsResult,
|
|
levelInfo: {
|
|
levelId: level.level_id,
|
|
levelName: level.level_name,
|
|
placeholder: level.placeholder,
|
|
isRequired: level.is_required,
|
|
isSearchable: level.is_searchable,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("계층 레벨 옵션 조회 실패", { error: error.message });
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "옵션 조회에 실패했습니다.",
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|
|
|