720 lines
20 KiB
TypeScript
720 lines
20 KiB
TypeScript
|
|
import { Request, Response } from "express";
|
||
|
|
import { getPool } from "../database/db";
|
||
|
|
import { logger } from "../utils/logger";
|
||
|
|
|
||
|
|
const pool = getPool();
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연쇄 관계 목록 조회
|
||
|
|
*/
|
||
|
|
export const getCascadingRelations = async (req: Request, res: Response) => {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user?.companyCode || "*";
|
||
|
|
const { isActive } = req.query;
|
||
|
|
|
||
|
|
let query = `
|
||
|
|
SELECT
|
||
|
|
relation_id,
|
||
|
|
relation_code,
|
||
|
|
relation_name,
|
||
|
|
description,
|
||
|
|
parent_table,
|
||
|
|
parent_value_column,
|
||
|
|
parent_label_column,
|
||
|
|
child_table,
|
||
|
|
child_filter_column,
|
||
|
|
child_value_column,
|
||
|
|
child_label_column,
|
||
|
|
child_order_column,
|
||
|
|
child_order_direction,
|
||
|
|
empty_parent_message,
|
||
|
|
no_options_message,
|
||
|
|
loading_message,
|
||
|
|
clear_on_parent_change,
|
||
|
|
company_code,
|
||
|
|
is_active,
|
||
|
|
created_by,
|
||
|
|
created_date,
|
||
|
|
updated_by,
|
||
|
|
updated_date
|
||
|
|
FROM cascading_relation
|
||
|
|
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) {
|
||
|
|
logger.error("연쇄 관계 목록 조회 실패", { error: error.message });
|
||
|
|
return res.status(500).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계 목록 조회에 실패했습니다.",
|
||
|
|
error: error.message,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연쇄 관계 상세 조회
|
||
|
|
*/
|
||
|
|
export const getCascadingRelationById = async (req: Request, res: Response) => {
|
||
|
|
try {
|
||
|
|
const { id } = req.params;
|
||
|
|
const companyCode = req.user?.companyCode || "*";
|
||
|
|
|
||
|
|
let query = `
|
||
|
|
SELECT
|
||
|
|
relation_id,
|
||
|
|
relation_code,
|
||
|
|
relation_name,
|
||
|
|
description,
|
||
|
|
parent_table,
|
||
|
|
parent_value_column,
|
||
|
|
parent_label_column,
|
||
|
|
child_table,
|
||
|
|
child_filter_column,
|
||
|
|
child_value_column,
|
||
|
|
child_label_column,
|
||
|
|
child_order_column,
|
||
|
|
child_order_direction,
|
||
|
|
empty_parent_message,
|
||
|
|
no_options_message,
|
||
|
|
loading_message,
|
||
|
|
clear_on_parent_change,
|
||
|
|
company_code,
|
||
|
|
is_active,
|
||
|
|
created_by,
|
||
|
|
created_date,
|
||
|
|
updated_by,
|
||
|
|
updated_date
|
||
|
|
FROM cascading_relation
|
||
|
|
WHERE relation_id = $1
|
||
|
|
`;
|
||
|
|
|
||
|
|
const params: any[] = [id];
|
||
|
|
|
||
|
|
// 멀티테넌시 필터링
|
||
|
|
if (companyCode !== "*") {
|
||
|
|
query += ` AND (company_code = $2 OR company_code = '*')`;
|
||
|
|
params.push(companyCode);
|
||
|
|
}
|
||
|
|
|
||
|
|
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) {
|
||
|
|
logger.error("연쇄 관계 상세 조회 실패", { error: error.message });
|
||
|
|
return res.status(500).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계 조회에 실패했습니다.",
|
||
|
|
error: error.message,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연쇄 관계 코드로 조회
|
||
|
|
*/
|
||
|
|
export const getCascadingRelationByCode = async (req: Request, res: Response) => {
|
||
|
|
try {
|
||
|
|
const { code } = req.params;
|
||
|
|
const companyCode = req.user?.companyCode || "*";
|
||
|
|
|
||
|
|
let query = `
|
||
|
|
SELECT
|
||
|
|
relation_id,
|
||
|
|
relation_code,
|
||
|
|
relation_name,
|
||
|
|
description,
|
||
|
|
parent_table,
|
||
|
|
parent_value_column,
|
||
|
|
parent_label_column,
|
||
|
|
child_table,
|
||
|
|
child_filter_column,
|
||
|
|
child_value_column,
|
||
|
|
child_label_column,
|
||
|
|
child_order_column,
|
||
|
|
child_order_direction,
|
||
|
|
empty_parent_message,
|
||
|
|
no_options_message,
|
||
|
|
loading_message,
|
||
|
|
clear_on_parent_change,
|
||
|
|
company_code,
|
||
|
|
is_active
|
||
|
|
FROM cascading_relation
|
||
|
|
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 += ` ORDER BY CASE WHEN company_code = $2 THEN 0 ELSE 1 END LIMIT 1`;
|
||
|
|
} else {
|
||
|
|
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) {
|
||
|
|
logger.error("연쇄 관계 코드 조회 실패", { error: error.message });
|
||
|
|
return res.status(500).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계 조회에 실패했습니다.",
|
||
|
|
error: error.message,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연쇄 관계 생성
|
||
|
|
*/
|
||
|
|
export const createCascadingRelation = async (req: Request, res: Response) => {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user?.companyCode || "*";
|
||
|
|
const userId = req.user?.userId || "system";
|
||
|
|
|
||
|
|
const {
|
||
|
|
relationCode,
|
||
|
|
relationName,
|
||
|
|
description,
|
||
|
|
parentTable,
|
||
|
|
parentValueColumn,
|
||
|
|
parentLabelColumn,
|
||
|
|
childTable,
|
||
|
|
childFilterColumn,
|
||
|
|
childValueColumn,
|
||
|
|
childLabelColumn,
|
||
|
|
childOrderColumn,
|
||
|
|
childOrderDirection,
|
||
|
|
emptyParentMessage,
|
||
|
|
noOptionsMessage,
|
||
|
|
loadingMessage,
|
||
|
|
clearOnParentChange,
|
||
|
|
} = req.body;
|
||
|
|
|
||
|
|
// 필수 필드 검증
|
||
|
|
if (!relationCode || !relationName || !parentTable || !parentValueColumn ||
|
||
|
|
!childTable || !childFilterColumn || !childValueColumn || !childLabelColumn) {
|
||
|
|
return res.status(400).json({
|
||
|
|
success: false,
|
||
|
|
message: "필수 필드가 누락되었습니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 중복 코드 체크
|
||
|
|
const duplicateCheck = await pool.query(
|
||
|
|
`SELECT relation_id FROM cascading_relation
|
||
|
|
WHERE relation_code = $1 AND company_code = $2`,
|
||
|
|
[relationCode, companyCode]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (duplicateCheck.rowCount && duplicateCheck.rowCount > 0) {
|
||
|
|
return res.status(400).json({
|
||
|
|
success: false,
|
||
|
|
message: "이미 존재하는 관계 코드입니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const query = `
|
||
|
|
INSERT INTO cascading_relation (
|
||
|
|
relation_code,
|
||
|
|
relation_name,
|
||
|
|
description,
|
||
|
|
parent_table,
|
||
|
|
parent_value_column,
|
||
|
|
parent_label_column,
|
||
|
|
child_table,
|
||
|
|
child_filter_column,
|
||
|
|
child_value_column,
|
||
|
|
child_label_column,
|
||
|
|
child_order_column,
|
||
|
|
child_order_direction,
|
||
|
|
empty_parent_message,
|
||
|
|
no_options_message,
|
||
|
|
loading_message,
|
||
|
|
clear_on_parent_change,
|
||
|
|
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, 'Y', $18, CURRENT_TIMESTAMP)
|
||
|
|
RETURNING *
|
||
|
|
`;
|
||
|
|
|
||
|
|
const result = await pool.query(query, [
|
||
|
|
relationCode,
|
||
|
|
relationName,
|
||
|
|
description || null,
|
||
|
|
parentTable,
|
||
|
|
parentValueColumn,
|
||
|
|
parentLabelColumn || null,
|
||
|
|
childTable,
|
||
|
|
childFilterColumn,
|
||
|
|
childValueColumn,
|
||
|
|
childLabelColumn,
|
||
|
|
childOrderColumn || null,
|
||
|
|
childOrderDirection || "ASC",
|
||
|
|
emptyParentMessage || "상위 항목을 먼저 선택하세요",
|
||
|
|
noOptionsMessage || "선택 가능한 항목이 없습니다",
|
||
|
|
loadingMessage || "로딩 중...",
|
||
|
|
clearOnParentChange !== false ? "Y" : "N",
|
||
|
|
companyCode,
|
||
|
|
userId,
|
||
|
|
]);
|
||
|
|
|
||
|
|
logger.info("연쇄 관계 생성", {
|
||
|
|
relationId: result.rows[0].relation_id,
|
||
|
|
relationCode,
|
||
|
|
companyCode,
|
||
|
|
userId,
|
||
|
|
});
|
||
|
|
|
||
|
|
return res.status(201).json({
|
||
|
|
success: true,
|
||
|
|
data: result.rows[0],
|
||
|
|
message: "연쇄 관계가 생성되었습니다.",
|
||
|
|
});
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("연쇄 관계 생성 실패", { error: error.message });
|
||
|
|
return res.status(500).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계 생성에 실패했습니다.",
|
||
|
|
error: error.message,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연쇄 관계 수정
|
||
|
|
*/
|
||
|
|
export const updateCascadingRelation = async (req: Request, res: Response) => {
|
||
|
|
try {
|
||
|
|
const { id } = req.params;
|
||
|
|
const companyCode = req.user?.companyCode || "*";
|
||
|
|
const userId = req.user?.userId || "system";
|
||
|
|
|
||
|
|
const {
|
||
|
|
relationName,
|
||
|
|
description,
|
||
|
|
parentTable,
|
||
|
|
parentValueColumn,
|
||
|
|
parentLabelColumn,
|
||
|
|
childTable,
|
||
|
|
childFilterColumn,
|
||
|
|
childValueColumn,
|
||
|
|
childLabelColumn,
|
||
|
|
childOrderColumn,
|
||
|
|
childOrderDirection,
|
||
|
|
emptyParentMessage,
|
||
|
|
noOptionsMessage,
|
||
|
|
loadingMessage,
|
||
|
|
clearOnParentChange,
|
||
|
|
isActive,
|
||
|
|
} = req.body;
|
||
|
|
|
||
|
|
// 권한 체크
|
||
|
|
const existingCheck = await pool.query(
|
||
|
|
`SELECT relation_id, company_code FROM cascading_relation WHERE relation_id = $1`,
|
||
|
|
[id]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (existingCheck.rowCount === 0) {
|
||
|
|
return res.status(404).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계를 찾을 수 없습니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 다른 회사의 데이터는 수정 불가 (최고 관리자 제외)
|
||
|
|
const existingCompanyCode = existingCheck.rows[0].company_code;
|
||
|
|
if (companyCode !== "*" && existingCompanyCode !== companyCode && existingCompanyCode !== "*") {
|
||
|
|
return res.status(403).json({
|
||
|
|
success: false,
|
||
|
|
message: "수정 권한이 없습니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const query = `
|
||
|
|
UPDATE cascading_relation SET
|
||
|
|
relation_name = COALESCE($1, relation_name),
|
||
|
|
description = COALESCE($2, description),
|
||
|
|
parent_table = COALESCE($3, parent_table),
|
||
|
|
parent_value_column = COALESCE($4, parent_value_column),
|
||
|
|
parent_label_column = COALESCE($5, parent_label_column),
|
||
|
|
child_table = COALESCE($6, child_table),
|
||
|
|
child_filter_column = COALESCE($7, child_filter_column),
|
||
|
|
child_value_column = COALESCE($8, child_value_column),
|
||
|
|
child_label_column = COALESCE($9, child_label_column),
|
||
|
|
child_order_column = COALESCE($10, child_order_column),
|
||
|
|
child_order_direction = COALESCE($11, child_order_direction),
|
||
|
|
empty_parent_message = COALESCE($12, empty_parent_message),
|
||
|
|
no_options_message = COALESCE($13, no_options_message),
|
||
|
|
loading_message = COALESCE($14, loading_message),
|
||
|
|
clear_on_parent_change = COALESCE($15, clear_on_parent_change),
|
||
|
|
is_active = COALESCE($16, is_active),
|
||
|
|
updated_by = $17,
|
||
|
|
updated_date = CURRENT_TIMESTAMP
|
||
|
|
WHERE relation_id = $18
|
||
|
|
RETURNING *
|
||
|
|
`;
|
||
|
|
|
||
|
|
const result = await pool.query(query, [
|
||
|
|
relationName,
|
||
|
|
description,
|
||
|
|
parentTable,
|
||
|
|
parentValueColumn,
|
||
|
|
parentLabelColumn,
|
||
|
|
childTable,
|
||
|
|
childFilterColumn,
|
||
|
|
childValueColumn,
|
||
|
|
childLabelColumn,
|
||
|
|
childOrderColumn,
|
||
|
|
childOrderDirection,
|
||
|
|
emptyParentMessage,
|
||
|
|
noOptionsMessage,
|
||
|
|
loadingMessage,
|
||
|
|
clearOnParentChange !== undefined ? (clearOnParentChange ? "Y" : "N") : null,
|
||
|
|
isActive !== undefined ? (isActive ? "Y" : "N") : null,
|
||
|
|
userId,
|
||
|
|
id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
logger.info("연쇄 관계 수정", {
|
||
|
|
relationId: id,
|
||
|
|
companyCode,
|
||
|
|
userId,
|
||
|
|
});
|
||
|
|
|
||
|
|
return res.json({
|
||
|
|
success: true,
|
||
|
|
data: result.rows[0],
|
||
|
|
message: "연쇄 관계가 수정되었습니다.",
|
||
|
|
});
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("연쇄 관계 수정 실패", { error: error.message });
|
||
|
|
return res.status(500).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계 수정에 실패했습니다.",
|
||
|
|
error: error.message,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연쇄 관계 삭제
|
||
|
|
*/
|
||
|
|
export const deleteCascadingRelation = async (req: Request, res: Response) => {
|
||
|
|
try {
|
||
|
|
const { id } = req.params;
|
||
|
|
const companyCode = req.user?.companyCode || "*";
|
||
|
|
const userId = req.user?.userId || "system";
|
||
|
|
|
||
|
|
// 권한 체크
|
||
|
|
const existingCheck = await pool.query(
|
||
|
|
`SELECT relation_id, company_code FROM cascading_relation WHERE relation_id = $1`,
|
||
|
|
[id]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (existingCheck.rowCount === 0) {
|
||
|
|
return res.status(404).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계를 찾을 수 없습니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 다른 회사의 데이터는 삭제 불가 (최고 관리자 제외)
|
||
|
|
const existingCompanyCode = existingCheck.rows[0].company_code;
|
||
|
|
if (companyCode !== "*" && existingCompanyCode !== companyCode && existingCompanyCode !== "*") {
|
||
|
|
return res.status(403).json({
|
||
|
|
success: false,
|
||
|
|
message: "삭제 권한이 없습니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 소프트 삭제 (is_active = 'N')
|
||
|
|
await pool.query(
|
||
|
|
`UPDATE cascading_relation SET is_active = 'N', updated_by = $1, updated_date = CURRENT_TIMESTAMP WHERE relation_id = $2`,
|
||
|
|
[userId, id]
|
||
|
|
);
|
||
|
|
|
||
|
|
logger.info("연쇄 관계 삭제", {
|
||
|
|
relationId: id,
|
||
|
|
companyCode,
|
||
|
|
userId,
|
||
|
|
});
|
||
|
|
|
||
|
|
return res.json({
|
||
|
|
success: true,
|
||
|
|
message: "연쇄 관계가 삭제되었습니다.",
|
||
|
|
});
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("연쇄 관계 삭제 실패", { error: error.message });
|
||
|
|
return res.status(500).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계 삭제에 실패했습니다.",
|
||
|
|
error: error.message,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 🆕 연쇄 관계로 부모 옵션 조회 (상위 선택 역할용)
|
||
|
|
* parent_table에서 전체 옵션을 조회합니다.
|
||
|
|
*/
|
||
|
|
export const getParentOptions = async (req: Request, res: Response) => {
|
||
|
|
try {
|
||
|
|
const { code } = req.params;
|
||
|
|
const companyCode = req.user?.companyCode || "*";
|
||
|
|
|
||
|
|
// 관계 정보 조회
|
||
|
|
let relationQuery = `
|
||
|
|
SELECT
|
||
|
|
parent_table,
|
||
|
|
parent_value_column,
|
||
|
|
parent_label_column
|
||
|
|
FROM cascading_relation
|
||
|
|
WHERE relation_code = $1
|
||
|
|
AND is_active = 'Y'
|
||
|
|
`;
|
||
|
|
|
||
|
|
const relationParams: any[] = [code];
|
||
|
|
|
||
|
|
if (companyCode !== "*") {
|
||
|
|
relationQuery += ` AND (company_code = $2 OR company_code = '*')`;
|
||
|
|
relationParams.push(companyCode);
|
||
|
|
relationQuery += ` ORDER BY CASE WHEN company_code = $2 THEN 0 ELSE 1 END LIMIT 1`;
|
||
|
|
} else {
|
||
|
|
relationQuery += ` LIMIT 1`;
|
||
|
|
}
|
||
|
|
|
||
|
|
const relationResult = await pool.query(relationQuery, relationParams);
|
||
|
|
|
||
|
|
if (relationResult.rowCount === 0) {
|
||
|
|
return res.status(404).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계를 찾을 수 없습니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const relation = relationResult.rows[0];
|
||
|
|
|
||
|
|
// 라벨 컬럼이 없으면 값 컬럼 사용
|
||
|
|
const labelColumn = relation.parent_label_column || relation.parent_value_column;
|
||
|
|
|
||
|
|
// 부모 옵션 조회
|
||
|
|
let optionsQuery = `
|
||
|
|
SELECT
|
||
|
|
${relation.parent_value_column} as value,
|
||
|
|
${labelColumn} as label
|
||
|
|
FROM ${relation.parent_table}
|
||
|
|
WHERE 1=1
|
||
|
|
`;
|
||
|
|
|
||
|
|
// 멀티테넌시 적용 (테이블에 company_code가 있는 경우)
|
||
|
|
const tableInfoResult = await pool.query(
|
||
|
|
`SELECT column_name FROM information_schema.columns
|
||
|
|
WHERE table_name = $1 AND column_name = 'company_code'`,
|
||
|
|
[relation.parent_table]
|
||
|
|
);
|
||
|
|
|
||
|
|
const optionsParams: any[] = [];
|
||
|
|
|
||
|
|
if (tableInfoResult.rowCount && tableInfoResult.rowCount > 0 && companyCode !== "*") {
|
||
|
|
optionsQuery += ` AND (company_code = $1 OR company_code = '*')`;
|
||
|
|
optionsParams.push(companyCode);
|
||
|
|
}
|
||
|
|
|
||
|
|
// status 컬럼이 있으면 활성 상태만 조회
|
||
|
|
const statusInfoResult = await pool.query(
|
||
|
|
`SELECT column_name FROM information_schema.columns
|
||
|
|
WHERE table_name = $1 AND column_name = 'status'`,
|
||
|
|
[relation.parent_table]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (statusInfoResult.rowCount && statusInfoResult.rowCount > 0) {
|
||
|
|
optionsQuery += ` AND (status IS NULL OR status != 'N')`;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 정렬
|
||
|
|
optionsQuery += ` ORDER BY ${labelColumn} ASC`;
|
||
|
|
|
||
|
|
const optionsResult = await pool.query(optionsQuery, optionsParams);
|
||
|
|
|
||
|
|
logger.info("부모 옵션 조회", {
|
||
|
|
relationCode: code,
|
||
|
|
parentTable: relation.parent_table,
|
||
|
|
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,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 연쇄 관계로 자식 옵션 조회
|
||
|
|
* 실제 연쇄 드롭다운에서 사용하는 API
|
||
|
|
*/
|
||
|
|
export const getCascadingOptions = async (req: Request, res: Response) => {
|
||
|
|
try {
|
||
|
|
const { code } = req.params;
|
||
|
|
const { parentValue } = req.query;
|
||
|
|
const companyCode = req.user?.companyCode || "*";
|
||
|
|
|
||
|
|
if (!parentValue) {
|
||
|
|
return res.json({
|
||
|
|
success: true,
|
||
|
|
data: [],
|
||
|
|
message: "부모 값이 없습니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 관계 정보 조회
|
||
|
|
let relationQuery = `
|
||
|
|
SELECT
|
||
|
|
child_table,
|
||
|
|
child_filter_column,
|
||
|
|
child_value_column,
|
||
|
|
child_label_column,
|
||
|
|
child_order_column,
|
||
|
|
child_order_direction
|
||
|
|
FROM cascading_relation
|
||
|
|
WHERE relation_code = $1
|
||
|
|
AND is_active = 'Y'
|
||
|
|
`;
|
||
|
|
|
||
|
|
const relationParams: any[] = [code];
|
||
|
|
|
||
|
|
if (companyCode !== "*") {
|
||
|
|
relationQuery += ` AND (company_code = $2 OR company_code = '*')`;
|
||
|
|
relationParams.push(companyCode);
|
||
|
|
relationQuery += ` ORDER BY CASE WHEN company_code = $2 THEN 0 ELSE 1 END LIMIT 1`;
|
||
|
|
} else {
|
||
|
|
relationQuery += ` LIMIT 1`;
|
||
|
|
}
|
||
|
|
|
||
|
|
const relationResult = await pool.query(relationQuery, relationParams);
|
||
|
|
|
||
|
|
if (relationResult.rowCount === 0) {
|
||
|
|
return res.status(404).json({
|
||
|
|
success: false,
|
||
|
|
message: "연쇄 관계를 찾을 수 없습니다.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const relation = relationResult.rows[0];
|
||
|
|
|
||
|
|
// 자식 옵션 조회
|
||
|
|
let optionsQuery = `
|
||
|
|
SELECT
|
||
|
|
${relation.child_value_column} as value,
|
||
|
|
${relation.child_label_column} as label
|
||
|
|
FROM ${relation.child_table}
|
||
|
|
WHERE ${relation.child_filter_column} = $1
|
||
|
|
`;
|
||
|
|
|
||
|
|
// 멀티테넌시 적용 (테이블에 company_code가 있는 경우)
|
||
|
|
const tableInfoResult = await pool.query(
|
||
|
|
`SELECT column_name FROM information_schema.columns
|
||
|
|
WHERE table_name = $1 AND column_name = 'company_code'`,
|
||
|
|
[relation.child_table]
|
||
|
|
);
|
||
|
|
|
||
|
|
const optionsParams: any[] = [parentValue];
|
||
|
|
|
||
|
|
if (tableInfoResult.rowCount && tableInfoResult.rowCount > 0 && companyCode !== "*") {
|
||
|
|
optionsQuery += ` AND (company_code = $2 OR company_code = '*')`;
|
||
|
|
optionsParams.push(companyCode);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 정렬
|
||
|
|
if (relation.child_order_column) {
|
||
|
|
optionsQuery += ` ORDER BY ${relation.child_order_column} ${relation.child_order_direction || "ASC"}`;
|
||
|
|
} else {
|
||
|
|
optionsQuery += ` ORDER BY ${relation.child_label_column} ASC`;
|
||
|
|
}
|
||
|
|
|
||
|
|
const optionsResult = await pool.query(optionsQuery, optionsParams);
|
||
|
|
|
||
|
|
logger.info("연쇄 옵션 조회", {
|
||
|
|
relationCode: code,
|
||
|
|
parentValue,
|
||
|
|
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,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|