ERP-node/backend-node/src/controllers/screenGroupController.ts

984 lines
35 KiB
TypeScript
Raw Normal View History

import { Request, Response } from "express";
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
// pool 인스턴스 가져오기
const pool = getPool();
// ============================================================
// 화면 그룹 (screen_groups) CRUD
// ============================================================
// 화면 그룹 목록 조회
export const getScreenGroups = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const { page = 1, size = 20, searchTerm } = req.query;
const offset = (parseInt(page as string) - 1) * parseInt(size as string);
let whereClause = "WHERE 1=1";
const params: any[] = [];
let paramIndex = 1;
// 회사 코드 필터링 (멀티테넌시)
if (companyCode !== "*") {
whereClause += ` AND company_code = $${paramIndex}`;
params.push(companyCode);
paramIndex++;
}
// 검색어 필터링
if (searchTerm) {
whereClause += ` AND (group_name ILIKE $${paramIndex} OR group_code ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`;
params.push(`%${searchTerm}%`);
paramIndex++;
}
// 전체 개수 조회
const countQuery = `SELECT COUNT(*) as total FROM screen_groups ${whereClause}`;
const countResult = await pool.query(countQuery, params);
const total = parseInt(countResult.rows[0].total);
// 데이터 조회
const dataQuery = `
SELECT
sg.*,
(SELECT COUNT(*) FROM screen_group_screens sgs WHERE sgs.group_id = sg.id) as screen_count
FROM screen_groups sg
${whereClause}
ORDER BY sg.display_order ASC, sg.created_date DESC
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
`;
params.push(parseInt(size as string), offset);
const result = await pool.query(dataQuery, params);
logger.info("화면 그룹 목록 조회", { companyCode, total, count: result.rows.length });
res.json({
success: true,
data: result.rows,
total,
page: parseInt(page as string),
size: parseInt(size as string),
totalPages: Math.ceil(total / parseInt(size as string)),
});
} catch (error: any) {
logger.error("화면 그룹 목록 조회 실패:", error);
res.status(500).json({ success: false, message: "화면 그룹 목록 조회에 실패했습니다.", error: error.message });
}
};
// 화면 그룹 상세 조회
export const getScreenGroup = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
let query = `
SELECT sg.*,
(SELECT json_agg(
json_build_object(
'id', sgs.id,
'screen_id', sgs.screen_id,
'screen_name', sd.screen_name,
'screen_role', sgs.screen_role,
'display_order', sgs.display_order,
'is_default', sgs.is_default
) ORDER BY sgs.display_order
) FROM screen_group_screens sgs
LEFT JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
WHERE sgs.group_id = sg.id
) as screens
FROM screen_groups sg
WHERE sg.id = $1
`;
const params: any[] = [id];
// 멀티테넌시 필터링
if (companyCode !== "*") {
query += ` AND sg.company_code = $2`;
params.push(companyCode);
}
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "화면 그룹을 찾을 수 없습니다." });
}
res.json({ success: true, data: result.rows[0] });
} catch (error: any) {
logger.error("화면 그룹 상세 조회 실패:", error);
res.status(500).json({ success: false, message: "화면 그룹 조회에 실패했습니다.", error: error.message });
}
};
// 화면 그룹 생성
export const createScreenGroup = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const userId = (req.user as any).userId;
const { group_name, group_code, main_table_name, description, icon, display_order, is_active } = req.body;
if (!group_name || !group_code) {
return res.status(400).json({ success: false, message: "그룹명과 그룹코드는 필수입니다." });
}
const query = `
INSERT INTO screen_groups (group_name, group_code, main_table_name, description, icon, display_order, is_active, company_code, writer)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING *
`;
const params = [
group_name,
group_code,
main_table_name || null,
description || null,
icon || null,
display_order || 0,
is_active || 'Y',
companyCode === "*" ? "*" : companyCode,
userId
];
const result = await pool.query(query, params);
logger.info("화면 그룹 생성", { companyCode, groupId: result.rows[0].id, groupName: group_name });
res.json({ success: true, data: result.rows[0], message: "화면 그룹이 생성되었습니다." });
} catch (error: any) {
logger.error("화면 그룹 생성 실패:", error);
if (error.code === '23505') {
return res.status(400).json({ success: false, message: "이미 존재하는 그룹 코드입니다." });
}
res.status(500).json({ success: false, message: "화면 그룹 생성에 실패했습니다.", error: error.message });
}
};
// 화면 그룹 수정
export const updateScreenGroup = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
const { group_name, group_code, main_table_name, description, icon, display_order, is_active } = req.body;
let query = `
UPDATE screen_groups
SET group_name = $1, group_code = $2, main_table_name = $3, description = $4,
icon = $5, display_order = $6, is_active = $7, updated_date = NOW()
WHERE id = $8
`;
const params: any[] = [group_name, group_code, main_table_name, description, icon, display_order, is_active, id];
// 멀티테넌시 필터링
if (companyCode !== "*") {
query += ` AND company_code = $9`;
params.push(companyCode);
}
query += " RETURNING *";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "화면 그룹을 찾을 수 없거나 권한이 없습니다." });
}
logger.info("화면 그룹 수정", { companyCode, groupId: id });
res.json({ success: true, data: result.rows[0], message: "화면 그룹이 수정되었습니다." });
} catch (error: any) {
logger.error("화면 그룹 수정 실패:", error);
res.status(500).json({ success: false, message: "화면 그룹 수정에 실패했습니다.", error: error.message });
}
};
// 화면 그룹 삭제
export const deleteScreenGroup = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
let query = `DELETE FROM screen_groups WHERE id = $1`;
const params: any[] = [id];
if (companyCode !== "*") {
query += ` AND company_code = $2`;
params.push(companyCode);
}
query += " RETURNING id";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "화면 그룹을 찾을 수 없거나 권한이 없습니다." });
}
logger.info("화면 그룹 삭제", { companyCode, groupId: id });
res.json({ success: true, message: "화면 그룹이 삭제되었습니다." });
} catch (error: any) {
logger.error("화면 그룹 삭제 실패:", error);
res.status(500).json({ success: false, message: "화면 그룹 삭제에 실패했습니다.", error: error.message });
}
};
// ============================================================
// 화면-그룹 연결 (screen_group_screens) CRUD
// ============================================================
// 그룹에 화면 추가
export const addScreenToGroup = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const userId = (req.user as any).userId;
const { group_id, screen_id, screen_role, display_order, is_default } = req.body;
if (!group_id || !screen_id) {
return res.status(400).json({ success: false, message: "그룹 ID와 화면 ID는 필수입니다." });
}
const query = `
INSERT INTO screen_group_screens (group_id, screen_id, screen_role, display_order, is_default, company_code, writer)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *
`;
const params = [
group_id,
screen_id,
screen_role || 'main',
display_order || 0,
is_default || 'N',
companyCode === "*" ? "*" : companyCode,
userId
];
const result = await pool.query(query, params);
logger.info("화면-그룹 연결 추가", { companyCode, groupId: group_id, screenId: screen_id });
res.json({ success: true, data: result.rows[0], message: "화면이 그룹에 추가되었습니다." });
} catch (error: any) {
logger.error("화면-그룹 연결 추가 실패:", error);
if (error.code === '23505') {
return res.status(400).json({ success: false, message: "이미 그룹에 추가된 화면입니다." });
}
res.status(500).json({ success: false, message: "화면 추가에 실패했습니다.", error: error.message });
}
};
// 그룹에서 화면 제거
export const removeScreenFromGroup = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
let query = `DELETE FROM screen_group_screens WHERE id = $1`;
const params: any[] = [id];
if (companyCode !== "*") {
query += ` AND company_code = $2`;
params.push(companyCode);
}
query += " RETURNING id";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "연결을 찾을 수 없거나 권한이 없습니다." });
}
logger.info("화면-그룹 연결 제거", { companyCode, id });
res.json({ success: true, message: "화면이 그룹에서 제거되었습니다." });
} catch (error: any) {
logger.error("화면-그룹 연결 제거 실패:", error);
res.status(500).json({ success: false, message: "화면 제거에 실패했습니다.", error: error.message });
}
};
// 그룹 내 화면 순서/역할 수정
export const updateScreenInGroup = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
const { screen_role, display_order, is_default } = req.body;
let query = `
UPDATE screen_group_screens
SET screen_role = $1, display_order = $2, is_default = $3, updated_date = NOW()
WHERE id = $4
`;
const params: any[] = [screen_role, display_order, is_default, id];
if (companyCode !== "*") {
query += ` AND company_code = $5`;
params.push(companyCode);
}
query += " RETURNING *";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "연결을 찾을 수 없거나 권한이 없습니다." });
}
res.json({ success: true, data: result.rows[0], message: "화면 정보가 수정되었습니다." });
} catch (error: any) {
logger.error("화면-그룹 연결 수정 실패:", error);
res.status(500).json({ success: false, message: "화면 정보 수정에 실패했습니다.", error: error.message });
}
};
// ============================================================
// 화면 필드 조인 설정 (screen_field_joins) CRUD
// ============================================================
// 화면 필드 조인 목록 조회
export const getFieldJoins = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const { screen_id } = req.query;
let query = `
SELECT sfj.*,
tl1.table_label as save_table_label,
tl2.table_label as join_table_label
FROM screen_field_joins sfj
LEFT JOIN table_labels tl1 ON sfj.save_table = tl1.table_name
LEFT JOIN table_labels tl2 ON sfj.join_table = tl2.table_name
WHERE 1=1
`;
const params: any[] = [];
let paramIndex = 1;
if (companyCode !== "*") {
query += ` AND sfj.company_code = $${paramIndex}`;
params.push(companyCode);
paramIndex++;
}
if (screen_id) {
query += ` AND sfj.screen_id = $${paramIndex}`;
params.push(screen_id);
paramIndex++;
}
query += " ORDER BY sfj.id ASC";
const result = await pool.query(query, params);
res.json({ success: true, data: result.rows });
} catch (error: any) {
logger.error("필드 조인 목록 조회 실패:", error);
res.status(500).json({ success: false, message: "필드 조인 목록 조회에 실패했습니다.", error: error.message });
}
};
// 화면 필드 조인 생성
export const createFieldJoin = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const userId = (req.user as any).userId;
const {
screen_id, layout_id, component_id, field_name,
save_table, save_column, join_table, join_column, display_column,
join_type, filter_condition, sort_column, sort_direction, is_active
} = req.body;
if (!screen_id || !save_table || !save_column || !join_table || !join_column || !display_column) {
return res.status(400).json({ success: false, message: "필수 필드가 누락되었습니다." });
}
const query = `
INSERT INTO screen_field_joins (
screen_id, layout_id, component_id, field_name,
save_table, save_column, join_table, join_column, display_column,
join_type, filter_condition, sort_column, sort_direction, is_active, company_code, writer
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
RETURNING *
`;
const params = [
screen_id, layout_id || null, component_id || null, field_name || null,
save_table, save_column, join_table, join_column, display_column,
join_type || 'LEFT', filter_condition || null, sort_column || null, sort_direction || 'ASC',
is_active || 'Y', companyCode === "*" ? "*" : companyCode, userId
];
const result = await pool.query(query, params);
logger.info("필드 조인 생성", { companyCode, screenId: screen_id, id: result.rows[0].id });
res.json({ success: true, data: result.rows[0], message: "필드 조인이 생성되었습니다." });
} catch (error: any) {
logger.error("필드 조인 생성 실패:", error);
res.status(500).json({ success: false, message: "필드 조인 생성에 실패했습니다.", error: error.message });
}
};
// 화면 필드 조인 수정
export const updateFieldJoin = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
const {
layout_id, component_id, field_name,
save_table, save_column, join_table, join_column, display_column,
join_type, filter_condition, sort_column, sort_direction, is_active
} = req.body;
let query = `
UPDATE screen_field_joins SET
layout_id = $1, component_id = $2, field_name = $3,
save_table = $4, save_column = $5, join_table = $6, join_column = $7, display_column = $8,
join_type = $9, filter_condition = $10, sort_column = $11, sort_direction = $12,
is_active = $13, updated_date = NOW()
WHERE id = $14
`;
const params: any[] = [
layout_id, component_id, field_name,
save_table, save_column, join_table, join_column, display_column,
join_type, filter_condition, sort_column, sort_direction, is_active, id
];
if (companyCode !== "*") {
query += ` AND company_code = $15`;
params.push(companyCode);
}
query += " RETURNING *";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "필드 조인을 찾을 수 없거나 권한이 없습니다." });
}
res.json({ success: true, data: result.rows[0], message: "필드 조인이 수정되었습니다." });
} catch (error: any) {
logger.error("필드 조인 수정 실패:", error);
res.status(500).json({ success: false, message: "필드 조인 수정에 실패했습니다.", error: error.message });
}
};
// 화면 필드 조인 삭제
export const deleteFieldJoin = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
let query = `DELETE FROM screen_field_joins WHERE id = $1`;
const params: any[] = [id];
if (companyCode !== "*") {
query += ` AND company_code = $2`;
params.push(companyCode);
}
query += " RETURNING id";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "필드 조인을 찾을 수 없거나 권한이 없습니다." });
}
res.json({ success: true, message: "필드 조인이 삭제되었습니다." });
} catch (error: any) {
logger.error("필드 조인 삭제 실패:", error);
res.status(500).json({ success: false, message: "필드 조인 삭제에 실패했습니다.", error: error.message });
}
};
// ============================================================
// 데이터 흐름 (screen_data_flows) CRUD
// ============================================================
// 데이터 흐름 목록 조회
export const getDataFlows = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const { group_id } = req.query;
let query = `
SELECT sdf.*,
sd1.screen_name as source_screen_name,
sd2.screen_name as target_screen_name,
sg.group_name
FROM screen_data_flows sdf
LEFT JOIN screen_definitions sd1 ON sdf.source_screen_id = sd1.screen_id
LEFT JOIN screen_definitions sd2 ON sdf.target_screen_id = sd2.screen_id
LEFT JOIN screen_groups sg ON sdf.group_id = sg.id
WHERE 1=1
`;
const params: any[] = [];
let paramIndex = 1;
if (companyCode !== "*") {
query += ` AND sdf.company_code = $${paramIndex}`;
params.push(companyCode);
paramIndex++;
}
if (group_id) {
query += ` AND sdf.group_id = $${paramIndex}`;
params.push(group_id);
paramIndex++;
}
query += " ORDER BY sdf.id ASC";
const result = await pool.query(query, params);
res.json({ success: true, data: result.rows });
} catch (error: any) {
logger.error("데이터 흐름 목록 조회 실패:", error);
res.status(500).json({ success: false, message: "데이터 흐름 목록 조회에 실패했습니다.", error: error.message });
}
};
// 데이터 흐름 생성
export const createDataFlow = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const userId = (req.user as any).userId;
const {
group_id, source_screen_id, source_action, target_screen_id, target_action,
data_mapping, flow_type, flow_label, condition_expression, is_active
} = req.body;
if (!source_screen_id || !target_screen_id) {
return res.status(400).json({ success: false, message: "소스 화면과 타겟 화면은 필수입니다." });
}
const query = `
INSERT INTO screen_data_flows (
group_id, source_screen_id, source_action, target_screen_id, target_action,
data_mapping, flow_type, flow_label, condition_expression, is_active, company_code, writer
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
RETURNING *
`;
const params = [
group_id || null, source_screen_id, source_action || null, target_screen_id, target_action || null,
data_mapping ? JSON.stringify(data_mapping) : null, flow_type || 'unidirectional',
flow_label || null, condition_expression || null, is_active || 'Y',
companyCode === "*" ? "*" : companyCode, userId
];
const result = await pool.query(query, params);
logger.info("데이터 흐름 생성", { companyCode, id: result.rows[0].id });
res.json({ success: true, data: result.rows[0], message: "데이터 흐름이 생성되었습니다." });
} catch (error: any) {
logger.error("데이터 흐름 생성 실패:", error);
res.status(500).json({ success: false, message: "데이터 흐름 생성에 실패했습니다.", error: error.message });
}
};
// 데이터 흐름 수정
export const updateDataFlow = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
const {
group_id, source_screen_id, source_action, target_screen_id, target_action,
data_mapping, flow_type, flow_label, condition_expression, is_active
} = req.body;
let query = `
UPDATE screen_data_flows SET
group_id = $1, source_screen_id = $2, source_action = $3,
target_screen_id = $4, target_action = $5, data_mapping = $6,
flow_type = $7, flow_label = $8, condition_expression = $9,
is_active = $10, updated_date = NOW()
WHERE id = $11
`;
const params: any[] = [
group_id, source_screen_id, source_action, target_screen_id, target_action,
data_mapping ? JSON.stringify(data_mapping) : null, flow_type, flow_label, condition_expression, is_active, id
];
if (companyCode !== "*") {
query += ` AND company_code = $12`;
params.push(companyCode);
}
query += " RETURNING *";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "데이터 흐름을 찾을 수 없거나 권한이 없습니다." });
}
res.json({ success: true, data: result.rows[0], message: "데이터 흐름이 수정되었습니다." });
} catch (error: any) {
logger.error("데이터 흐름 수정 실패:", error);
res.status(500).json({ success: false, message: "데이터 흐름 수정에 실패했습니다.", error: error.message });
}
};
// 데이터 흐름 삭제
export const deleteDataFlow = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
let query = `DELETE FROM screen_data_flows WHERE id = $1`;
const params: any[] = [id];
if (companyCode !== "*") {
query += ` AND company_code = $2`;
params.push(companyCode);
}
query += " RETURNING id";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "데이터 흐름을 찾을 수 없거나 권한이 없습니다." });
}
res.json({ success: true, message: "데이터 흐름이 삭제되었습니다." });
} catch (error: any) {
logger.error("데이터 흐름 삭제 실패:", error);
res.status(500).json({ success: false, message: "데이터 흐름 삭제에 실패했습니다.", error: error.message });
}
};
// ============================================================
// 화면-테이블 관계 (screen_table_relations) CRUD
// ============================================================
// 화면-테이블 관계 목록 조회
export const getTableRelations = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const { screen_id, group_id } = req.query;
let query = `
SELECT str.*,
sd.screen_name,
sg.group_name,
tl.table_label
FROM screen_table_relations str
LEFT JOIN screen_definitions sd ON str.screen_id = sd.screen_id
LEFT JOIN screen_groups sg ON str.group_id = sg.id
LEFT JOIN table_labels tl ON str.table_name = tl.table_name
WHERE 1=1
`;
const params: any[] = [];
let paramIndex = 1;
if (companyCode !== "*") {
query += ` AND str.company_code = $${paramIndex}`;
params.push(companyCode);
paramIndex++;
}
if (screen_id) {
query += ` AND str.screen_id = $${paramIndex}`;
params.push(screen_id);
paramIndex++;
}
if (group_id) {
query += ` AND str.group_id = $${paramIndex}`;
params.push(group_id);
paramIndex++;
}
query += " ORDER BY str.id ASC";
const result = await pool.query(query, params);
res.json({ success: true, data: result.rows });
} catch (error: any) {
logger.error("화면-테이블 관계 목록 조회 실패:", error);
res.status(500).json({ success: false, message: "화면-테이블 관계 목록 조회에 실패했습니다.", error: error.message });
}
};
// 화면-테이블 관계 생성
export const createTableRelation = async (req: Request, res: Response) => {
try {
const companyCode = (req.user as any).companyCode;
const userId = (req.user as any).userId;
const { group_id, screen_id, table_name, relation_type, crud_operations, description, is_active } = req.body;
if (!screen_id || !table_name) {
return res.status(400).json({ success: false, message: "화면 ID와 테이블명은 필수입니다." });
}
const query = `
INSERT INTO screen_table_relations (group_id, screen_id, table_name, relation_type, crud_operations, description, is_active, company_code, writer)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING *
`;
const params = [
group_id || null, screen_id, table_name, relation_type || 'main',
crud_operations || 'CRUD', description || null, is_active || 'Y',
companyCode === "*" ? "*" : companyCode, userId
];
const result = await pool.query(query, params);
logger.info("화면-테이블 관계 생성", { companyCode, screenId: screen_id, tableName: table_name });
res.json({ success: true, data: result.rows[0], message: "화면-테이블 관계가 생성되었습니다." });
} catch (error: any) {
logger.error("화면-테이블 관계 생성 실패:", error);
res.status(500).json({ success: false, message: "화면-테이블 관계 생성에 실패했습니다.", error: error.message });
}
};
// 화면-테이블 관계 수정
export const updateTableRelation = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
const { group_id, table_name, relation_type, crud_operations, description, is_active } = req.body;
let query = `
UPDATE screen_table_relations SET
group_id = $1, table_name = $2, relation_type = $3, crud_operations = $4,
description = $5, is_active = $6, updated_date = NOW()
WHERE id = $7
`;
const params: any[] = [group_id, table_name, relation_type, crud_operations, description, is_active, id];
if (companyCode !== "*") {
query += ` AND company_code = $8`;
params.push(companyCode);
}
query += " RETURNING *";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "화면-테이블 관계를 찾을 수 없거나 권한이 없습니다." });
}
res.json({ success: true, data: result.rows[0], message: "화면-테이블 관계가 수정되었습니다." });
} catch (error: any) {
logger.error("화면-테이블 관계 수정 실패:", error);
res.status(500).json({ success: false, message: "화면-테이블 관계 수정에 실패했습니다.", error: error.message });
}
};
// 화면-테이블 관계 삭제
export const deleteTableRelation = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const companyCode = (req.user as any).companyCode;
let query = `DELETE FROM screen_table_relations WHERE id = $1`;
const params: any[] = [id];
if (companyCode !== "*") {
query += ` AND company_code = $2`;
params.push(companyCode);
}
query += " RETURNING id";
const result = await pool.query(query, params);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: "화면-테이블 관계를 찾을 수 없거나 권한이 없습니다." });
}
res.json({ success: true, message: "화면-테이블 관계가 삭제되었습니다." });
} catch (error: any) {
logger.error("화면-테이블 관계 삭제 실패:", error);
res.status(500).json({ success: false, message: "화면-테이블 관계 삭제에 실패했습니다.", error: error.message });
}
};
// ============================================================
// 화면 레이아웃 요약 정보 (미리보기용)
// ============================================================
// 화면 레이아웃 요약 조회 (위젯 타입별 개수, 라벨 목록)
export const getScreenLayoutSummary = async (req: Request, res: Response) => {
try {
const { screenId } = req.params;
// 화면의 컴포넌트 정보 조회
const query = `
SELECT
properties->>'widgetType' as widget_type,
properties->>'label' as label,
properties->>'fieldName' as field_name,
properties->>'tableName' as table_name
FROM screen_layouts
WHERE screen_id = $1
AND component_type = 'component'
ORDER BY display_order ASC
`;
const result = await pool.query(query, [screenId]);
// 위젯 타입별 집계
const widgetCounts: Record<string, number> = {};
const labels: string[] = [];
const fields: Array<{ label: string; widgetType: string; fieldName?: string }> = [];
result.rows.forEach((row: any) => {
const widgetType = row.widget_type || 'text';
widgetCounts[widgetType] = (widgetCounts[widgetType] || 0) + 1;
if (row.label && row.label !== '기본 버튼') {
labels.push(row.label);
fields.push({
label: row.label,
widgetType: widgetType,
fieldName: row.field_name,
});
}
});
// 화면 타입 추론 (가장 많은 컴포넌트 기준)
let screenType = 'form'; // 기본값
if (widgetCounts['table'] > 0) {
screenType = 'grid';
} else if (widgetCounts['custom'] > 2) {
screenType = 'dashboard';
} else if (Object.keys(widgetCounts).length <= 2 && widgetCounts['button'] > 0) {
screenType = 'action';
}
logger.info("화면 레이아웃 요약 조회", { screenId, widgetCounts, fieldCount: fields.length });
res.json({
success: true,
data: {
screenId: parseInt(screenId),
screenType,
widgetCounts,
totalComponents: result.rows.length,
fields: fields.slice(0, 10), // 최대 10개
labels: labels.slice(0, 8), // 최대 8개
},
});
} catch (error: any) {
logger.error("화면 레이아웃 요약 조회 실패:", error);
res.status(500).json({ success: false, message: "화면 레이아웃 요약 조회에 실패했습니다.", error: error.message });
}
};
// 여러 화면의 레이아웃 요약 일괄 조회 (미니어처 렌더링용 좌표 포함)
export const getMultipleScreenLayoutSummary = async (req: Request, res: Response) => {
try {
const { screenIds } = req.body;
if (!screenIds || !Array.isArray(screenIds) || screenIds.length === 0) {
return res.status(400).json({ success: false, message: "screenIds 배열이 필요합니다." });
}
// 여러 화면의 컴포넌트 정보 (좌표 포함) 한번에 조회
// componentType이 더 정확한 위젯 종류 (table-list, button-primary 등)
const query = `
SELECT
screen_id,
component_type,
position_x,
position_y,
width,
height,
properties->>'componentType' as component_kind,
properties->>'widgetType' as widget_type,
properties->>'label' as label
FROM screen_layouts
WHERE screen_id = ANY($1)
AND component_type = 'component'
ORDER BY screen_id, display_order ASC
`;
const result = await pool.query(query, [screenIds]);
// 화면별로 그룹핑
const summaryMap: Record<number, any> = {};
screenIds.forEach((id: number) => {
summaryMap[id] = {
screenId: id,
screenType: 'form',
widgetCounts: {},
totalComponents: 0,
// 미니어처 렌더링용 레이아웃 데이터
layoutItems: [],
canvasWidth: 0,
canvasHeight: 0,
};
});
result.rows.forEach((row: any) => {
const screenId = row.screen_id;
// componentKind가 더 정확한 타입 (table-list, button-primary, table-search-widget 등)
const componentKind = row.component_kind || row.widget_type || 'text';
const widgetType = row.widget_type || 'text';
if (summaryMap[screenId]) {
summaryMap[screenId].widgetCounts[componentKind] =
(summaryMap[screenId].widgetCounts[componentKind] || 0) + 1;
summaryMap[screenId].totalComponents++;
// 레이아웃 아이템 추가 (미니어처 렌더링용)
summaryMap[screenId].layoutItems.push({
x: row.position_x || 0,
y: row.position_y || 0,
width: row.width || 100,
height: row.height || 30,
componentKind: componentKind, // 정확한 컴포넌트 종류
widgetType: widgetType,
label: row.label,
});
// 캔버스 크기 계산 (최대 좌표 기준)
const rightEdge = (row.position_x || 0) + (row.width || 100);
const bottomEdge = (row.position_y || 0) + (row.height || 30);
if (rightEdge > summaryMap[screenId].canvasWidth) {
summaryMap[screenId].canvasWidth = rightEdge;
}
if (bottomEdge > summaryMap[screenId].canvasHeight) {
summaryMap[screenId].canvasHeight = bottomEdge;
}
}
});
// 화면 타입 추론 (componentKind 기준)
Object.values(summaryMap).forEach((summary: any) => {
if (summary.widgetCounts['table-list'] > 0) {
summary.screenType = 'grid';
} else if (summary.widgetCounts['table-search-widget'] > 1) {
summary.screenType = 'dashboard';
} else if (summary.totalComponents <= 5 && summary.widgetCounts['button-primary'] > 0) {
summary.screenType = 'action';
}
});
logger.info("여러 화면 레이아웃 요약 조회", { screenIds, count: Object.keys(summaryMap).length });
res.json({
success: true,
data: summaryMap,
});
} catch (error: any) {
logger.error("여러 화면 레이아웃 요약 조회 실패:", error);
res.status(500).json({ success: false, message: "여러 화면 레이아웃 요약 조회에 실패했습니다.", error: error.message });
}
};