2025-12-18 14:12:48 +09:00
|
|
|
import { Response } from "express";
|
|
|
|
|
import { AuthenticatedRequest } from "../types/auth";
|
|
|
|
|
import { getPool } from "../database/db";
|
|
|
|
|
import { logger } from "../utils/logger";
|
|
|
|
|
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
// 카테고리 값 연쇄관계 그룹 CRUD
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 연쇄관계 그룹 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
export const getCategoryValueCascadingGroups = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
const { isActive } = req.query;
|
|
|
|
|
|
|
|
|
|
let query = `
|
|
|
|
|
SELECT
|
|
|
|
|
group_id,
|
|
|
|
|
relation_code,
|
|
|
|
|
relation_name,
|
|
|
|
|
description,
|
|
|
|
|
parent_table_name,
|
|
|
|
|
parent_column_name,
|
|
|
|
|
parent_menu_objid,
|
|
|
|
|
child_table_name,
|
|
|
|
|
child_column_name,
|
|
|
|
|
child_menu_objid,
|
|
|
|
|
clear_on_parent_change,
|
|
|
|
|
show_group_label,
|
|
|
|
|
empty_parent_message,
|
|
|
|
|
no_options_message,
|
|
|
|
|
company_code,
|
|
|
|
|
is_active,
|
|
|
|
|
created_by,
|
|
|
|
|
created_date,
|
|
|
|
|
updated_by,
|
|
|
|
|
updated_date
|
|
|
|
|
FROM category_value_cascading_group
|
|
|
|
|
WHERE 1=1
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const params: any[] = [];
|
|
|
|
|
let paramIndex = 1;
|
|
|
|
|
|
|
|
|
|
// 멀티테넌시 필터링
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
query += ` AND (company_code = $${paramIndex} OR company_code = '*')`;
|
|
|
|
|
params.push(companyCode);
|
|
|
|
|
paramIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isActive !== undefined) {
|
|
|
|
|
query += ` AND is_active = $${paramIndex}`;
|
|
|
|
|
params.push(isActive);
|
|
|
|
|
paramIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query += ` ORDER BY relation_name ASC`;
|
|
|
|
|
|
|
|
|
|
const result = await pool.query(query, params);
|
|
|
|
|
|
|
|
|
|
logger.info("카테고리 값 연쇄관계 그룹 목록 조회", {
|
|
|
|
|
companyCode,
|
|
|
|
|
count: result.rowCount,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.rows,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
2025-12-18 15:16:34 +09:00
|
|
|
logger.error("카테고리 값 연쇄관계 그룹 목록 조회 실패", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹 목록 조회에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 연쇄관계 그룹 상세 조회
|
|
|
|
|
*/
|
|
|
|
|
export const getCategoryValueCascadingGroupById = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { groupId } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
|
|
|
|
|
// 그룹 정보 조회
|
|
|
|
|
let groupQuery = `
|
|
|
|
|
SELECT
|
|
|
|
|
group_id,
|
|
|
|
|
relation_code,
|
|
|
|
|
relation_name,
|
|
|
|
|
description,
|
|
|
|
|
parent_table_name,
|
|
|
|
|
parent_column_name,
|
|
|
|
|
parent_menu_objid,
|
|
|
|
|
child_table_name,
|
|
|
|
|
child_column_name,
|
|
|
|
|
child_menu_objid,
|
|
|
|
|
clear_on_parent_change,
|
|
|
|
|
show_group_label,
|
|
|
|
|
empty_parent_message,
|
|
|
|
|
no_options_message,
|
|
|
|
|
company_code,
|
|
|
|
|
is_active
|
|
|
|
|
FROM category_value_cascading_group
|
|
|
|
|
WHERE group_id = $1
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const groupParams: any[] = [groupId];
|
|
|
|
|
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
groupQuery += ` AND (company_code = $2 OR company_code = '*')`;
|
|
|
|
|
groupParams.push(companyCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const groupResult = await pool.query(groupQuery, groupParams);
|
|
|
|
|
|
|
|
|
|
if (groupResult.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹을 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 매핑 정보 조회
|
|
|
|
|
const mappingQuery = `
|
|
|
|
|
SELECT
|
|
|
|
|
mapping_id,
|
|
|
|
|
parent_value_code,
|
|
|
|
|
parent_value_label,
|
|
|
|
|
child_value_code,
|
|
|
|
|
child_value_label,
|
|
|
|
|
display_order,
|
|
|
|
|
is_active
|
|
|
|
|
FROM category_value_cascading_mapping
|
|
|
|
|
WHERE group_id = $1 AND is_active = 'Y'
|
|
|
|
|
ORDER BY parent_value_code, display_order, child_value_label
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const mappingResult = await pool.query(mappingQuery, [groupId]);
|
|
|
|
|
|
|
|
|
|
// 부모 값별로 자식 값 그룹화
|
|
|
|
|
const mappingsByParent: Record<string, any[]> = {};
|
|
|
|
|
for (const row of mappingResult.rows) {
|
|
|
|
|
const parentKey = row.parent_value_code;
|
|
|
|
|
if (!mappingsByParent[parentKey]) {
|
|
|
|
|
mappingsByParent[parentKey] = [];
|
|
|
|
|
}
|
|
|
|
|
mappingsByParent[parentKey].push({
|
|
|
|
|
childValueCode: row.child_value_code,
|
|
|
|
|
childValueLabel: row.child_value_label,
|
|
|
|
|
displayOrder: row.display_order,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
...groupResult.rows[0],
|
|
|
|
|
mappings: mappingResult.rows,
|
|
|
|
|
mappingsByParent,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
2025-12-18 15:16:34 +09:00
|
|
|
logger.error("카테고리 값 연쇄관계 그룹 상세 조회 실패", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹 조회에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 관계 코드로 조회
|
|
|
|
|
*/
|
|
|
|
|
export const getCategoryValueCascadingByCode = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { code } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
|
|
|
|
|
let query = `
|
|
|
|
|
SELECT
|
|
|
|
|
group_id,
|
|
|
|
|
relation_code,
|
|
|
|
|
relation_name,
|
|
|
|
|
description,
|
|
|
|
|
parent_table_name,
|
|
|
|
|
parent_column_name,
|
|
|
|
|
parent_menu_objid,
|
|
|
|
|
child_table_name,
|
|
|
|
|
child_column_name,
|
|
|
|
|
child_menu_objid,
|
|
|
|
|
clear_on_parent_change,
|
|
|
|
|
show_group_label,
|
|
|
|
|
empty_parent_message,
|
|
|
|
|
no_options_message,
|
|
|
|
|
company_code,
|
|
|
|
|
is_active
|
|
|
|
|
FROM category_value_cascading_group
|
|
|
|
|
WHERE relation_code = $1 AND is_active = 'Y'
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const params: any[] = [code];
|
|
|
|
|
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
query += ` AND (company_code = $2 OR company_code = '*')`;
|
|
|
|
|
params.push(companyCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query += ` LIMIT 1`;
|
|
|
|
|
|
|
|
|
|
const result = await pool.query(query, params);
|
|
|
|
|
|
|
|
|
|
if (result.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계를 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.rows[0],
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
2025-12-18 15:16:34 +09:00
|
|
|
logger.error("카테고리 값 연쇄관계 코드 조회 실패", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 조회에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 연쇄관계 그룹 생성
|
|
|
|
|
*/
|
|
|
|
|
export const createCategoryValueCascadingGroup = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
const userId = req.user?.userId || "system";
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
relationCode,
|
|
|
|
|
relationName,
|
|
|
|
|
description,
|
|
|
|
|
parentTableName,
|
|
|
|
|
parentColumnName,
|
|
|
|
|
parentMenuObjid,
|
|
|
|
|
childTableName,
|
|
|
|
|
childColumnName,
|
|
|
|
|
childMenuObjid,
|
|
|
|
|
clearOnParentChange = true,
|
|
|
|
|
showGroupLabel = true,
|
|
|
|
|
emptyParentMessage,
|
|
|
|
|
noOptionsMessage,
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
// 필수 필드 검증
|
2025-12-18 15:16:34 +09:00
|
|
|
if (
|
|
|
|
|
!relationCode ||
|
|
|
|
|
!relationName ||
|
|
|
|
|
!parentTableName ||
|
|
|
|
|
!parentColumnName ||
|
|
|
|
|
!childTableName ||
|
|
|
|
|
!childColumnName
|
|
|
|
|
) {
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "필수 필드가 누락되었습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 중복 코드 체크
|
|
|
|
|
const duplicateCheck = await pool.query(
|
|
|
|
|
`SELECT group_id FROM category_value_cascading_group
|
|
|
|
|
WHERE relation_code = $1 AND (company_code = $2 OR company_code = '*')`,
|
|
|
|
|
[relationCode, companyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (duplicateCheck.rowCount && duplicateCheck.rowCount > 0) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "이미 존재하는 관계 코드입니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const query = `
|
|
|
|
|
INSERT INTO category_value_cascading_group (
|
|
|
|
|
relation_code,
|
|
|
|
|
relation_name,
|
|
|
|
|
description,
|
|
|
|
|
parent_table_name,
|
|
|
|
|
parent_column_name,
|
|
|
|
|
parent_menu_objid,
|
|
|
|
|
child_table_name,
|
|
|
|
|
child_column_name,
|
|
|
|
|
child_menu_objid,
|
|
|
|
|
clear_on_parent_change,
|
|
|
|
|
show_group_label,
|
|
|
|
|
empty_parent_message,
|
|
|
|
|
no_options_message,
|
|
|
|
|
company_code,
|
|
|
|
|
is_active,
|
|
|
|
|
created_by,
|
|
|
|
|
created_date
|
|
|
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, 'Y', $15, NOW())
|
|
|
|
|
RETURNING *
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const result = await pool.query(query, [
|
|
|
|
|
relationCode,
|
|
|
|
|
relationName,
|
|
|
|
|
description || null,
|
|
|
|
|
parentTableName,
|
|
|
|
|
parentColumnName,
|
|
|
|
|
parentMenuObjid || null,
|
|
|
|
|
childTableName,
|
|
|
|
|
childColumnName,
|
|
|
|
|
childMenuObjid || null,
|
|
|
|
|
clearOnParentChange ? "Y" : "N",
|
|
|
|
|
showGroupLabel ? "Y" : "N",
|
|
|
|
|
emptyParentMessage || "상위 항목을 먼저 선택하세요",
|
|
|
|
|
noOptionsMessage || "선택 가능한 항목이 없습니다",
|
|
|
|
|
companyCode,
|
|
|
|
|
userId,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
logger.info("카테고리 값 연쇄관계 그룹 생성", {
|
|
|
|
|
groupId: result.rows[0].group_id,
|
|
|
|
|
relationCode,
|
|
|
|
|
companyCode,
|
|
|
|
|
userId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.status(201).json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.rows[0],
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹이 생성되었습니다.",
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
2025-12-18 15:16:34 +09:00
|
|
|
logger.error("카테고리 값 연쇄관계 그룹 생성 실패", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹 생성에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 연쇄관계 그룹 수정
|
|
|
|
|
*/
|
|
|
|
|
export const updateCategoryValueCascadingGroup = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { groupId } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
const userId = req.user?.userId || "system";
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
relationName,
|
|
|
|
|
description,
|
|
|
|
|
parentTableName,
|
|
|
|
|
parentColumnName,
|
|
|
|
|
parentMenuObjid,
|
|
|
|
|
childTableName,
|
|
|
|
|
childColumnName,
|
|
|
|
|
childMenuObjid,
|
|
|
|
|
clearOnParentChange,
|
|
|
|
|
showGroupLabel,
|
|
|
|
|
emptyParentMessage,
|
|
|
|
|
noOptionsMessage,
|
|
|
|
|
isActive,
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
// 권한 체크
|
|
|
|
|
const existingCheck = await pool.query(
|
|
|
|
|
`SELECT group_id, company_code FROM category_value_cascading_group WHERE group_id = $1`,
|
|
|
|
|
[groupId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existingCheck.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹을 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingCompanyCode = existingCheck.rows[0].company_code;
|
2025-12-18 15:16:34 +09:00
|
|
|
if (
|
|
|
|
|
companyCode !== "*" &&
|
|
|
|
|
existingCompanyCode !== companyCode &&
|
|
|
|
|
existingCompanyCode !== "*"
|
|
|
|
|
) {
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(403).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "수정 권한이 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const query = `
|
|
|
|
|
UPDATE category_value_cascading_group SET
|
|
|
|
|
relation_name = COALESCE($1, relation_name),
|
|
|
|
|
description = COALESCE($2, description),
|
|
|
|
|
parent_table_name = COALESCE($3, parent_table_name),
|
|
|
|
|
parent_column_name = COALESCE($4, parent_column_name),
|
|
|
|
|
parent_menu_objid = COALESCE($5, parent_menu_objid),
|
|
|
|
|
child_table_name = COALESCE($6, child_table_name),
|
|
|
|
|
child_column_name = COALESCE($7, child_column_name),
|
|
|
|
|
child_menu_objid = COALESCE($8, child_menu_objid),
|
|
|
|
|
clear_on_parent_change = COALESCE($9, clear_on_parent_change),
|
|
|
|
|
show_group_label = COALESCE($10, show_group_label),
|
|
|
|
|
empty_parent_message = COALESCE($11, empty_parent_message),
|
|
|
|
|
no_options_message = COALESCE($12, no_options_message),
|
|
|
|
|
is_active = COALESCE($13, is_active),
|
|
|
|
|
updated_by = $14,
|
|
|
|
|
updated_date = NOW()
|
|
|
|
|
WHERE group_id = $15
|
|
|
|
|
RETURNING *
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const result = await pool.query(query, [
|
|
|
|
|
relationName,
|
|
|
|
|
description,
|
|
|
|
|
parentTableName,
|
|
|
|
|
parentColumnName,
|
|
|
|
|
parentMenuObjid,
|
|
|
|
|
childTableName,
|
|
|
|
|
childColumnName,
|
|
|
|
|
childMenuObjid,
|
2025-12-18 15:16:34 +09:00
|
|
|
clearOnParentChange !== undefined
|
|
|
|
|
? clearOnParentChange
|
|
|
|
|
? "Y"
|
|
|
|
|
: "N"
|
|
|
|
|
: null,
|
2025-12-18 14:12:48 +09:00
|
|
|
showGroupLabel !== undefined ? (showGroupLabel ? "Y" : "N") : null,
|
|
|
|
|
emptyParentMessage,
|
|
|
|
|
noOptionsMessage,
|
|
|
|
|
isActive !== undefined ? (isActive ? "Y" : "N") : null,
|
|
|
|
|
userId,
|
|
|
|
|
groupId,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
logger.info("카테고리 값 연쇄관계 그룹 수정", {
|
|
|
|
|
groupId,
|
|
|
|
|
companyCode,
|
|
|
|
|
userId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.rows[0],
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹이 수정되었습니다.",
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
2025-12-18 15:16:34 +09:00
|
|
|
logger.error("카테고리 값 연쇄관계 그룹 수정 실패", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹 수정에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 연쇄관계 그룹 삭제
|
|
|
|
|
*/
|
|
|
|
|
export const deleteCategoryValueCascadingGroup = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { groupId } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
const userId = req.user?.userId || "system";
|
|
|
|
|
|
|
|
|
|
// 권한 체크
|
|
|
|
|
const existingCheck = await pool.query(
|
|
|
|
|
`SELECT group_id, company_code FROM category_value_cascading_group WHERE group_id = $1`,
|
|
|
|
|
[groupId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existingCheck.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹을 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingCompanyCode = existingCheck.rows[0].company_code;
|
2025-12-18 15:16:34 +09:00
|
|
|
if (
|
|
|
|
|
companyCode !== "*" &&
|
|
|
|
|
existingCompanyCode !== companyCode &&
|
|
|
|
|
existingCompanyCode !== "*"
|
|
|
|
|
) {
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(403).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "삭제 권한이 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 소프트 삭제
|
|
|
|
|
await pool.query(
|
|
|
|
|
`UPDATE category_value_cascading_group
|
|
|
|
|
SET is_active = 'N', updated_by = $1, updated_date = NOW()
|
|
|
|
|
WHERE group_id = $2`,
|
|
|
|
|
[userId, groupId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.info("카테고리 값 연쇄관계 그룹 삭제", {
|
|
|
|
|
groupId,
|
|
|
|
|
companyCode,
|
|
|
|
|
userId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹이 삭제되었습니다.",
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
2025-12-18 15:16:34 +09:00
|
|
|
logger.error("카테고리 값 연쇄관계 그룹 삭제 실패", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹 삭제에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
// 카테고리 값 연쇄관계 매핑 CRUD
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 매핑 일괄 저장 (기존 매핑 교체)
|
|
|
|
|
*/
|
|
|
|
|
export const saveCategoryValueCascadingMappings = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { groupId } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
const { mappings } = req.body; // [{ parentValueCode, parentValueLabel, childValueCode, childValueLabel, displayOrder }]
|
|
|
|
|
|
|
|
|
|
if (!Array.isArray(mappings)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "mappings는 배열이어야 합니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 그룹 존재 확인
|
|
|
|
|
const groupCheck = await pool.query(
|
|
|
|
|
`SELECT group_id FROM category_value_cascading_group WHERE group_id = $1 AND is_active = 'Y'`,
|
|
|
|
|
[groupId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (groupCheck.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 그룹을 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 트랜잭션으로 처리
|
|
|
|
|
const client = await pool.connect();
|
|
|
|
|
try {
|
|
|
|
|
await client.query("BEGIN");
|
|
|
|
|
|
|
|
|
|
// 기존 매핑 삭제 (하드 삭제)
|
|
|
|
|
await client.query(
|
|
|
|
|
`DELETE FROM category_value_cascading_mapping WHERE group_id = $1`,
|
|
|
|
|
[groupId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 새 매핑 삽입
|
|
|
|
|
if (mappings.length > 0) {
|
|
|
|
|
const insertQuery = `
|
|
|
|
|
INSERT INTO category_value_cascading_mapping (
|
|
|
|
|
group_id, parent_value_code, parent_value_label,
|
|
|
|
|
child_value_code, child_value_label, display_order,
|
|
|
|
|
company_code, is_active, created_date
|
|
|
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, 'Y', NOW())
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
for (const mapping of mappings) {
|
|
|
|
|
await client.query(insertQuery, [
|
|
|
|
|
groupId,
|
|
|
|
|
mapping.parentValueCode,
|
|
|
|
|
mapping.parentValueLabel || null,
|
|
|
|
|
mapping.childValueCode,
|
|
|
|
|
mapping.childValueLabel || null,
|
|
|
|
|
mapping.displayOrder || 0,
|
|
|
|
|
companyCode,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await client.query("COMMIT");
|
|
|
|
|
|
|
|
|
|
logger.info("카테고리 값 연쇄관계 매핑 저장", {
|
|
|
|
|
groupId,
|
|
|
|
|
mappingCount: mappings.length,
|
|
|
|
|
companyCode,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: `${mappings.length}개의 매핑이 저장되었습니다.`,
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
await client.query("ROLLBACK");
|
|
|
|
|
throw err;
|
|
|
|
|
} finally {
|
|
|
|
|
client.release();
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
2025-12-18 15:16:34 +09:00
|
|
|
logger.error("카테고리 값 연쇄관계 매핑 저장 실패", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
2025-12-18 14:12:48 +09:00
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계 매핑 저장에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
// 연쇄 옵션 조회 (실제 드롭다운에서 사용)
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 연쇄 옵션 조회
|
|
|
|
|
* 부모 값(들)에 해당하는 자식 카테고리 값 목록 반환
|
|
|
|
|
* 다중 부모값 지원
|
|
|
|
|
*/
|
|
|
|
|
export const getCategoryValueCascadingOptions = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { code } = req.params;
|
|
|
|
|
const { parentValue, parentValues } = req.query;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
|
|
|
|
|
// 다중 부모값 파싱
|
|
|
|
|
let parentValueArray: string[] = [];
|
2025-12-18 15:16:34 +09:00
|
|
|
|
2025-12-18 14:12:48 +09:00
|
|
|
if (parentValues) {
|
|
|
|
|
if (Array.isArray(parentValues)) {
|
2025-12-18 15:16:34 +09:00
|
|
|
parentValueArray = parentValues.map((v) => String(v));
|
2025-12-18 14:12:48 +09:00
|
|
|
} else {
|
2025-12-18 15:16:34 +09:00
|
|
|
parentValueArray = String(parentValues)
|
|
|
|
|
.split(",")
|
|
|
|
|
.map((v) => v.trim())
|
|
|
|
|
.filter((v) => v);
|
2025-12-18 14:12:48 +09:00
|
|
|
}
|
|
|
|
|
} else if (parentValue) {
|
|
|
|
|
parentValueArray = [String(parentValue)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parentValueArray.length === 0) {
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: [],
|
|
|
|
|
message: "부모 값이 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 관계 정보 조회
|
|
|
|
|
let groupQuery = `
|
|
|
|
|
SELECT group_id, show_group_label
|
|
|
|
|
FROM category_value_cascading_group
|
|
|
|
|
WHERE relation_code = $1 AND is_active = 'Y'
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const groupParams: any[] = [code];
|
|
|
|
|
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
groupQuery += ` AND (company_code = $2 OR company_code = '*')`;
|
|
|
|
|
groupParams.push(companyCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
groupQuery += ` LIMIT 1`;
|
|
|
|
|
|
|
|
|
|
const groupResult = await pool.query(groupQuery, groupParams);
|
|
|
|
|
|
|
|
|
|
if (groupResult.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계를 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const group = groupResult.rows[0];
|
|
|
|
|
|
|
|
|
|
// 매핑된 자식 값 조회 (다중 부모값에 대해 IN 절 사용)
|
2025-12-18 15:16:34 +09:00
|
|
|
const placeholders = parentValueArray
|
|
|
|
|
.map((_, idx) => `$${idx + 2}`)
|
|
|
|
|
.join(", ");
|
|
|
|
|
|
2025-12-18 14:12:48 +09:00
|
|
|
const optionsQuery = `
|
|
|
|
|
SELECT DISTINCT
|
|
|
|
|
child_value_code as value,
|
|
|
|
|
child_value_label as label,
|
|
|
|
|
parent_value_code as parent_value,
|
|
|
|
|
parent_value_label as parent_label,
|
|
|
|
|
display_order
|
|
|
|
|
FROM category_value_cascading_mapping
|
|
|
|
|
WHERE group_id = $1
|
|
|
|
|
AND parent_value_code IN (${placeholders})
|
|
|
|
|
AND is_active = 'Y'
|
|
|
|
|
ORDER BY parent_value_code, display_order, child_value_label
|
|
|
|
|
`;
|
|
|
|
|
|
2025-12-18 15:16:34 +09:00
|
|
|
const optionsResult = await pool.query(optionsQuery, [
|
|
|
|
|
group.group_id,
|
|
|
|
|
...parentValueArray,
|
|
|
|
|
]);
|
2025-12-18 14:12:48 +09:00
|
|
|
|
|
|
|
|
logger.info("카테고리 값 연쇄 옵션 조회", {
|
|
|
|
|
relationCode: code,
|
|
|
|
|
parentValues: parentValueArray,
|
|
|
|
|
optionsCount: optionsResult.rowCount,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: optionsResult.rows,
|
2025-12-18 15:16:34 +09:00
|
|
|
showGroupLabel: group.show_group_label === "Y",
|
2025-12-18 14:12:48 +09:00
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error("카테고리 값 연쇄 옵션 조회 실패", { error: error.message });
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄 옵션 조회에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 부모 카테고리 값 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
export const getCategoryValueCascadingParentOptions = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { code } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
|
|
|
|
|
// 관계 정보 조회
|
|
|
|
|
let groupQuery = `
|
|
|
|
|
SELECT
|
|
|
|
|
group_id,
|
|
|
|
|
parent_table_name,
|
|
|
|
|
parent_column_name,
|
|
|
|
|
parent_menu_objid
|
|
|
|
|
FROM category_value_cascading_group
|
|
|
|
|
WHERE relation_code = $1 AND is_active = 'Y'
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const groupParams: any[] = [code];
|
|
|
|
|
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
groupQuery += ` AND (company_code = $2 OR company_code = '*')`;
|
|
|
|
|
groupParams.push(companyCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
groupQuery += ` LIMIT 1`;
|
|
|
|
|
|
|
|
|
|
const groupResult = await pool.query(groupQuery, groupParams);
|
|
|
|
|
|
|
|
|
|
if (groupResult.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계를 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const group = groupResult.rows[0];
|
|
|
|
|
|
|
|
|
|
// 부모 카테고리 값 조회 (table_column_category_values에서)
|
|
|
|
|
let optionsQuery = `
|
|
|
|
|
SELECT
|
|
|
|
|
value_code as value,
|
|
|
|
|
value_label as label,
|
|
|
|
|
value_order as display_order
|
|
|
|
|
FROM table_column_category_values
|
|
|
|
|
WHERE table_name = $1
|
|
|
|
|
AND column_name = $2
|
|
|
|
|
AND is_active = true
|
|
|
|
|
`;
|
|
|
|
|
|
2025-12-18 15:16:34 +09:00
|
|
|
const optionsParams: any[] = [
|
|
|
|
|
group.parent_table_name,
|
|
|
|
|
group.parent_column_name,
|
|
|
|
|
];
|
2025-12-18 14:12:48 +09:00
|
|
|
let paramIndex = 3;
|
|
|
|
|
|
|
|
|
|
// 메뉴 스코프 적용
|
|
|
|
|
if (group.parent_menu_objid) {
|
|
|
|
|
optionsQuery += ` AND menu_objid = $${paramIndex}`;
|
|
|
|
|
optionsParams.push(group.parent_menu_objid);
|
|
|
|
|
paramIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 멀티테넌시 적용
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
optionsQuery += ` AND (company_code = $${paramIndex} OR company_code = '*')`;
|
|
|
|
|
optionsParams.push(companyCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
optionsQuery += ` ORDER BY value_order, value_label`;
|
|
|
|
|
|
|
|
|
|
const optionsResult = await pool.query(optionsQuery, optionsParams);
|
|
|
|
|
|
|
|
|
|
logger.info("부모 카테고리 값 조회", {
|
|
|
|
|
relationCode: code,
|
|
|
|
|
tableName: group.parent_table_name,
|
|
|
|
|
columnName: group.parent_column_name,
|
|
|
|
|
optionsCount: optionsResult.rowCount,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: optionsResult.rows,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error("부모 카테고리 값 조회 실패", { error: error.message });
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "부모 카테고리 값 조회에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 자식 카테고리 값 목록 조회 (매핑 설정 UI용)
|
|
|
|
|
*/
|
|
|
|
|
export const getCategoryValueCascadingChildOptions = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { code } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
|
|
|
|
|
// 관계 정보 조회
|
|
|
|
|
let groupQuery = `
|
|
|
|
|
SELECT
|
|
|
|
|
group_id,
|
|
|
|
|
child_table_name,
|
|
|
|
|
child_column_name,
|
|
|
|
|
child_menu_objid
|
|
|
|
|
FROM category_value_cascading_group
|
|
|
|
|
WHERE relation_code = $1 AND is_active = 'Y'
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const groupParams: any[] = [code];
|
|
|
|
|
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
groupQuery += ` AND (company_code = $2 OR company_code = '*')`;
|
|
|
|
|
groupParams.push(companyCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
groupQuery += ` LIMIT 1`;
|
|
|
|
|
|
|
|
|
|
const groupResult = await pool.query(groupQuery, groupParams);
|
|
|
|
|
|
|
|
|
|
if (groupResult.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 연쇄관계를 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const group = groupResult.rows[0];
|
|
|
|
|
|
|
|
|
|
// 자식 카테고리 값 조회 (table_column_category_values에서)
|
|
|
|
|
let optionsQuery = `
|
|
|
|
|
SELECT
|
|
|
|
|
value_code as value,
|
|
|
|
|
value_label as label,
|
|
|
|
|
value_order as display_order
|
|
|
|
|
FROM table_column_category_values
|
|
|
|
|
WHERE table_name = $1
|
|
|
|
|
AND column_name = $2
|
|
|
|
|
AND is_active = true
|
|
|
|
|
`;
|
|
|
|
|
|
2025-12-18 15:16:34 +09:00
|
|
|
const optionsParams: any[] = [
|
|
|
|
|
group.child_table_name,
|
|
|
|
|
group.child_column_name,
|
|
|
|
|
];
|
2025-12-18 14:12:48 +09:00
|
|
|
let paramIndex = 3;
|
|
|
|
|
|
|
|
|
|
// 메뉴 스코프 적용
|
|
|
|
|
if (group.child_menu_objid) {
|
|
|
|
|
optionsQuery += ` AND menu_objid = $${paramIndex}`;
|
|
|
|
|
optionsParams.push(group.child_menu_objid);
|
|
|
|
|
paramIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 멀티테넌시 적용
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
optionsQuery += ` AND (company_code = $${paramIndex} OR company_code = '*')`;
|
|
|
|
|
optionsParams.push(companyCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
optionsQuery += ` ORDER BY value_order, value_label`;
|
|
|
|
|
|
|
|
|
|
const optionsResult = await pool.query(optionsQuery, optionsParams);
|
|
|
|
|
|
|
|
|
|
logger.info("자식 카테고리 값 조회", {
|
|
|
|
|
relationCode: code,
|
|
|
|
|
tableName: group.child_table_name,
|
|
|
|
|
columnName: group.child_column_name,
|
|
|
|
|
optionsCount: optionsResult.rowCount,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: optionsResult.rows,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error("자식 카테고리 값 조회 실패", { error: error.message });
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "자식 카테고리 값 조회에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-18 15:16:34 +09:00
|
|
|
/**
|
|
|
|
|
* 테이블명으로 해당 테이블의 모든 연쇄관계 매핑 조회
|
|
|
|
|
* (테이블 목록에서 코드→라벨 변환에 사용)
|
|
|
|
|
*/
|
|
|
|
|
export const getCategoryValueCascadingMappingsByTable = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const { tableName } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
|
|
|
|
|
if (!tableName) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "테이블명이 필요합니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 해당 테이블이 자식 테이블인 연쇄관계 그룹 찾기
|
|
|
|
|
let groupQuery = `
|
|
|
|
|
SELECT
|
|
|
|
|
group_id,
|
|
|
|
|
relation_code,
|
|
|
|
|
child_column_name
|
|
|
|
|
FROM category_value_cascading_group
|
|
|
|
|
WHERE child_table_name = $1
|
|
|
|
|
AND is_active = 'Y'
|
|
|
|
|
`;
|
|
|
|
|
const groupParams: any[] = [tableName];
|
|
|
|
|
let paramIndex = 2;
|
|
|
|
|
|
|
|
|
|
// 멀티테넌시 적용
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
groupQuery += ` AND (company_code = $${paramIndex} OR company_code = '*')`;
|
|
|
|
|
groupParams.push(companyCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const groupResult = await pool.query(groupQuery, groupParams);
|
|
|
|
|
|
|
|
|
|
if (groupResult.rowCount === 0) {
|
|
|
|
|
// 연쇄관계가 없으면 빈 객체 반환
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 각 그룹의 매핑 조회
|
|
|
|
|
const mappings: Record<string, Array<{ code: string; label: string }>> = {};
|
|
|
|
|
|
|
|
|
|
for (const group of groupResult.rows) {
|
|
|
|
|
const mappingQuery = `
|
|
|
|
|
SELECT DISTINCT
|
|
|
|
|
child_value_code as code,
|
|
|
|
|
child_value_label as label
|
|
|
|
|
FROM category_value_cascading_mapping
|
|
|
|
|
WHERE group_id = $1
|
|
|
|
|
AND is_active = 'Y'
|
|
|
|
|
ORDER BY child_value_label
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const mappingResult = await pool.query(mappingQuery, [group.group_id]);
|
|
|
|
|
|
|
|
|
|
if (mappingResult.rowCount && mappingResult.rowCount > 0) {
|
|
|
|
|
mappings[group.child_column_name] = mappingResult.rows;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("테이블별 연쇄관계 매핑 조회", {
|
|
|
|
|
tableName,
|
|
|
|
|
groupCount: groupResult.rowCount,
|
|
|
|
|
columnMappings: Object.keys(mappings),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: mappings,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error("테이블별 연쇄관계 매핑 조회 실패", { error: error.message });
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "연쇄관계 매핑 조회에 실패했습니다.",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|