Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/v2-unified-renewal
This commit is contained in:
@@ -1417,6 +1417,75 @@ export async function updateMenu(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 재귀적으로 모든 하위 메뉴 ID를 수집하는 헬퍼 함수
|
||||
*/
|
||||
async function collectAllChildMenuIds(parentObjid: number): Promise<number[]> {
|
||||
const allIds: number[] = [];
|
||||
|
||||
// 직접 자식 메뉴들 조회
|
||||
const children = await query<any>(
|
||||
`SELECT objid FROM menu_info WHERE parent_obj_id = $1`,
|
||||
[parentObjid]
|
||||
);
|
||||
|
||||
for (const child of children) {
|
||||
allIds.push(child.objid);
|
||||
// 자식의 자식들도 재귀적으로 수집
|
||||
const grandChildren = await collectAllChildMenuIds(child.objid);
|
||||
allIds.push(...grandChildren);
|
||||
}
|
||||
|
||||
return allIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 및 관련 데이터 정리 헬퍼 함수
|
||||
*/
|
||||
async function cleanupMenuRelatedData(menuObjid: number): Promise<void> {
|
||||
// 1. category_column_mapping에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE category_column_mapping SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 2. code_category에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE code_category SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 3. code_info에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE code_info SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 4. numbering_rules에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE numbering_rules SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 5. rel_menu_auth에서 관련 권한 삭제
|
||||
await query(
|
||||
`DELETE FROM rel_menu_auth WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 6. screen_menu_assignments에서 관련 할당 삭제
|
||||
await query(
|
||||
`DELETE FROM screen_menu_assignments WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 7. screen_groups에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE screen_groups SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 삭제
|
||||
*/
|
||||
@@ -1443,7 +1512,7 @@ export async function deleteMenu(
|
||||
|
||||
// 삭제하려는 메뉴 조회
|
||||
const currentMenu = await queryOne<any>(
|
||||
`SELECT objid, company_code FROM menu_info WHERE objid = $1`,
|
||||
`SELECT objid, company_code, menu_name_kor FROM menu_info WHERE objid = $1`,
|
||||
[Number(menuId)]
|
||||
);
|
||||
|
||||
@@ -1478,67 +1547,50 @@ export async function deleteMenu(
|
||||
}
|
||||
}
|
||||
|
||||
// 외래키 제약 조건이 있는 관련 테이블 데이터 먼저 정리
|
||||
const menuObjid = Number(menuId);
|
||||
|
||||
// 1. category_column_mapping에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE category_column_mapping SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
// 하위 메뉴들 재귀적으로 수집
|
||||
const childMenuIds = await collectAllChildMenuIds(menuObjid);
|
||||
const allMenuIdsToDelete = [menuObjid, ...childMenuIds];
|
||||
|
||||
// 2. code_category에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE code_category SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 3. code_info에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE code_info SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 4. numbering_rules에서 menu_objid를 NULL로 설정
|
||||
await query(
|
||||
`UPDATE numbering_rules SET menu_objid = NULL WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 5. rel_menu_auth에서 관련 권한 삭제
|
||||
await query(
|
||||
`DELETE FROM rel_menu_auth WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
// 6. screen_menu_assignments에서 관련 할당 삭제
|
||||
await query(
|
||||
`DELETE FROM screen_menu_assignments WHERE menu_objid = $1`,
|
||||
[menuObjid]
|
||||
);
|
||||
logger.info(`메뉴 삭제 대상: 본인(${menuObjid}) + 하위 메뉴 ${childMenuIds.length}개`, {
|
||||
menuName: currentMenu.menu_name_kor,
|
||||
totalCount: allMenuIdsToDelete.length,
|
||||
childMenuIds,
|
||||
});
|
||||
|
||||
logger.info("메뉴 관련 데이터 정리 완료", { menuObjid });
|
||||
// 모든 삭제 대상 메뉴에 대해 관련 데이터 정리
|
||||
for (const objid of allMenuIdsToDelete) {
|
||||
await cleanupMenuRelatedData(objid);
|
||||
}
|
||||
|
||||
// Raw Query를 사용한 메뉴 삭제
|
||||
const [deletedMenu] = await query<any>(
|
||||
`DELETE FROM menu_info WHERE objid = $1 RETURNING *`,
|
||||
[menuObjid]
|
||||
);
|
||||
logger.info("메뉴 관련 데이터 정리 완료", {
|
||||
menuObjid,
|
||||
totalCleaned: allMenuIdsToDelete.length
|
||||
});
|
||||
|
||||
logger.info("메뉴 삭제 성공", { deletedMenu });
|
||||
// 하위 메뉴부터 역순으로 삭제 (외래키 제약 회피)
|
||||
// 가장 깊은 하위부터 삭제해야 하므로 역순으로
|
||||
const reversedIds = [...allMenuIdsToDelete].reverse();
|
||||
|
||||
for (const objid of reversedIds) {
|
||||
await query(`DELETE FROM menu_info WHERE objid = $1`, [objid]);
|
||||
}
|
||||
|
||||
logger.info("메뉴 삭제 성공", {
|
||||
deletedMenuObjid: menuObjid,
|
||||
deletedMenuName: currentMenu.menu_name_kor,
|
||||
totalDeleted: allMenuIdsToDelete.length,
|
||||
});
|
||||
|
||||
const response: ApiResponse<any> = {
|
||||
success: true,
|
||||
message: "메뉴가 성공적으로 삭제되었습니다.",
|
||||
message: `메뉴가 성공적으로 삭제되었습니다. (하위 메뉴 ${childMenuIds.length}개 포함)`,
|
||||
data: {
|
||||
objid: deletedMenu.objid.toString(),
|
||||
menuNameKor: deletedMenu.menu_name_kor,
|
||||
menuNameEng: deletedMenu.menu_name_eng,
|
||||
menuUrl: deletedMenu.menu_url,
|
||||
menuDesc: deletedMenu.menu_desc,
|
||||
status: deletedMenu.status,
|
||||
writer: deletedMenu.writer,
|
||||
regdate: new Date(deletedMenu.regdate).toISOString(),
|
||||
objid: menuObjid.toString(),
|
||||
menuNameKor: currentMenu.menu_name_kor,
|
||||
deletedCount: allMenuIdsToDelete.length,
|
||||
deletedChildCount: childMenuIds.length,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1623,18 +1675,49 @@ export async function deleteMenusBatch(
|
||||
}
|
||||
}
|
||||
|
||||
// 모든 삭제 대상 메뉴 ID 수집 (하위 메뉴 포함)
|
||||
const allMenuIdsToDelete = new Set<number>();
|
||||
|
||||
for (const menuId of menuIds) {
|
||||
const objid = Number(menuId);
|
||||
allMenuIdsToDelete.add(objid);
|
||||
|
||||
// 하위 메뉴들 재귀적으로 수집
|
||||
const childMenuIds = await collectAllChildMenuIds(objid);
|
||||
childMenuIds.forEach(id => allMenuIdsToDelete.add(Number(id)));
|
||||
}
|
||||
|
||||
const allIdsArray = Array.from(allMenuIdsToDelete);
|
||||
|
||||
logger.info(`메뉴 일괄 삭제 대상: 선택 ${menuIds.length}개 + 하위 메뉴 포함 총 ${allIdsArray.length}개`, {
|
||||
selectedMenuIds: menuIds,
|
||||
totalWithChildren: allIdsArray.length,
|
||||
});
|
||||
|
||||
// 모든 삭제 대상 메뉴에 대해 관련 데이터 정리
|
||||
for (const objid of allIdsArray) {
|
||||
await cleanupMenuRelatedData(objid);
|
||||
}
|
||||
|
||||
logger.info("메뉴 관련 데이터 정리 완료", {
|
||||
totalCleaned: allIdsArray.length
|
||||
});
|
||||
|
||||
// Raw Query를 사용한 메뉴 일괄 삭제
|
||||
let deletedCount = 0;
|
||||
let failedCount = 0;
|
||||
const deletedMenus: any[] = [];
|
||||
const failedMenuIds: string[] = [];
|
||||
|
||||
// 하위 메뉴부터 삭제하기 위해 역순으로 정렬
|
||||
const reversedIds = [...allIdsArray].reverse();
|
||||
|
||||
// 각 메뉴 ID에 대해 삭제 시도
|
||||
for (const menuId of menuIds) {
|
||||
for (const menuObjid of reversedIds) {
|
||||
try {
|
||||
const result = await query<any>(
|
||||
`DELETE FROM menu_info WHERE objid = $1 RETURNING *`,
|
||||
[Number(menuId)]
|
||||
[menuObjid]
|
||||
);
|
||||
|
||||
if (result.length > 0) {
|
||||
@@ -1645,20 +1728,20 @@ export async function deleteMenusBatch(
|
||||
});
|
||||
} else {
|
||||
failedCount++;
|
||||
failedMenuIds.push(menuId);
|
||||
failedMenuIds.push(String(menuObjid));
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`메뉴 삭제 실패 (ID: ${menuId}):`, error);
|
||||
logger.error(`메뉴 삭제 실패 (ID: ${menuObjid}):`, error);
|
||||
failedCount++;
|
||||
failedMenuIds.push(menuId);
|
||||
failedMenuIds.push(String(menuObjid));
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("메뉴 일괄 삭제 완료", {
|
||||
total: menuIds.length,
|
||||
requested: menuIds.length,
|
||||
totalWithChildren: allIdsArray.length,
|
||||
deletedCount,
|
||||
failedCount,
|
||||
deletedMenus,
|
||||
failedMenuIds,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { Request, Response } from "express";
|
||||
import { getPool } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import {
|
||||
syncScreenGroupsToMenu,
|
||||
syncMenuToScreenGroups,
|
||||
getSyncStatus,
|
||||
syncAllCompanies,
|
||||
} from "../services/menuScreenSyncService";
|
||||
|
||||
// pool 인스턴스 가져오기
|
||||
const pool = getPool();
|
||||
@@ -10,9 +17,9 @@ const pool = getPool();
|
||||
// ============================================================
|
||||
|
||||
// 화면 그룹 목록 조회
|
||||
export const getScreenGroups = async (req: Request, res: Response) => {
|
||||
export const getScreenGroups = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const { page = 1, size = 20, searchTerm } = req.query;
|
||||
const offset = (parseInt(page as string) - 1) * parseInt(size as string);
|
||||
|
||||
@@ -84,10 +91,10 @@ export const getScreenGroups = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면 그룹 상세 조회
|
||||
export const getScreenGroup = async (req: Request, res: Response) => {
|
||||
export const getScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
let query = `
|
||||
SELECT sg.*,
|
||||
@@ -130,10 +137,10 @@ export const getScreenGroup = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면 그룹 생성
|
||||
export const createScreenGroup = async (req: Request, res: Response) => {
|
||||
export const createScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const userCompanyCode = (req.user as any).companyCode;
|
||||
const userId = (req.user as any).userId;
|
||||
const userCompanyCode = req.user?.companyCode || "*";
|
||||
const userId = req.user?.userId || "";
|
||||
const { group_name, group_code, main_table_name, description, icon, display_order, is_active, parent_group_id, target_company_code } = req.body;
|
||||
|
||||
if (!group_name || !group_code) {
|
||||
@@ -204,10 +211,10 @@ export const createScreenGroup = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면 그룹 수정
|
||||
export const updateScreenGroup = async (req: Request, res: Response) => {
|
||||
export const updateScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userCompanyCode = (req.user as any).companyCode;
|
||||
const userCompanyCode = req.user?.companyCode || "*";
|
||||
const { group_name, group_code, main_table_name, description, icon, display_order, is_active, parent_group_id, target_company_code } = req.body;
|
||||
|
||||
// 회사 코드 결정: 최고 관리자가 특정 회사를 선택한 경우 해당 회사로, 아니면 현재 그룹의 회사 유지
|
||||
@@ -293,11 +300,36 @@ export const updateScreenGroup = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면 그룹 삭제
|
||||
export const deleteScreenGroup = async (req: Request, res: Response) => {
|
||||
export const deleteScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
await client.query('BEGIN');
|
||||
|
||||
// 1. 삭제할 그룹과 하위 그룹 ID 수집 (CASCADE 삭제 대상)
|
||||
const childGroupsResult = await client.query(`
|
||||
WITH RECURSIVE child_groups AS (
|
||||
SELECT id FROM screen_groups WHERE id = $1
|
||||
UNION ALL
|
||||
SELECT sg.id FROM screen_groups sg
|
||||
JOIN child_groups cg ON sg.parent_group_id = cg.id
|
||||
)
|
||||
SELECT id FROM child_groups
|
||||
`, [id]);
|
||||
const groupIdsToDelete = childGroupsResult.rows.map((r: any) => r.id);
|
||||
|
||||
// 2. menu_info에서 삭제될 screen_group 참조를 NULL로 정리
|
||||
if (groupIdsToDelete.length > 0) {
|
||||
await client.query(`
|
||||
UPDATE menu_info
|
||||
SET screen_group_id = NULL
|
||||
WHERE screen_group_id = ANY($1::int[])
|
||||
`, [groupIdsToDelete]);
|
||||
}
|
||||
|
||||
// 3. screen_groups 삭제
|
||||
let query = `DELETE FROM screen_groups WHERE id = $1`;
|
||||
const params: any[] = [id];
|
||||
|
||||
@@ -308,18 +340,24 @@ export const deleteScreenGroup = async (req: Request, res: Response) => {
|
||||
|
||||
query += " RETURNING id";
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
const result = await client.query(query, params);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(404).json({ success: false, message: "화면 그룹을 찾을 수 없거나 권한이 없습니다." });
|
||||
}
|
||||
|
||||
logger.info("화면 그룹 삭제", { companyCode, groupId: id });
|
||||
await client.query('COMMIT');
|
||||
|
||||
logger.info("화면 그룹 삭제", { companyCode, groupId: id, cleanedRefs: groupIdsToDelete.length });
|
||||
|
||||
res.json({ success: true, message: "화면 그룹이 삭제되었습니다." });
|
||||
} catch (error: any) {
|
||||
await client.query('ROLLBACK');
|
||||
logger.error("화면 그룹 삭제 실패:", error);
|
||||
res.status(500).json({ success: false, message: "화면 그룹 삭제에 실패했습니다.", error: error.message });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -329,10 +367,10 @@ export const deleteScreenGroup = async (req: Request, res: Response) => {
|
||||
// ============================================================
|
||||
|
||||
// 그룹에 화면 추가
|
||||
export const addScreenToGroup = async (req: Request, res: Response) => {
|
||||
export const addScreenToGroup = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const userId = (req.user as any).userId;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const userId = req.user?.userId || "";
|
||||
const { group_id, screen_id, screen_role, display_order, is_default } = req.body;
|
||||
|
||||
if (!group_id || !screen_id) {
|
||||
@@ -369,10 +407,10 @@ export const addScreenToGroup = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 그룹에서 화면 제거
|
||||
export const removeScreenFromGroup = async (req: Request, res: Response) => {
|
||||
export const removeScreenFromGroup = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
let query = `DELETE FROM screen_group_screens WHERE id = $1`;
|
||||
const params: any[] = [id];
|
||||
@@ -400,10 +438,10 @@ export const removeScreenFromGroup = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 그룹 내 화면 순서/역할 수정
|
||||
export const updateScreenInGroup = async (req: Request, res: Response) => {
|
||||
export const updateScreenInGroup = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const { screen_role, display_order, is_default } = req.body;
|
||||
|
||||
let query = `
|
||||
@@ -439,9 +477,9 @@ export const updateScreenInGroup = async (req: Request, res: Response) => {
|
||||
// ============================================================
|
||||
|
||||
// 화면 필드 조인 목록 조회
|
||||
export const getFieldJoins = async (req: Request, res: Response) => {
|
||||
export const getFieldJoins = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const { screen_id } = req.query;
|
||||
|
||||
let query = `
|
||||
@@ -480,10 +518,10 @@ export const getFieldJoins = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면 필드 조인 생성
|
||||
export const createFieldJoin = async (req: Request, res: Response) => {
|
||||
export const createFieldJoin = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const userId = (req.user as any).userId;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const userId = req.user?.userId || "";
|
||||
const {
|
||||
screen_id, layout_id, component_id, field_name,
|
||||
save_table, save_column, join_table, join_column, display_column,
|
||||
@@ -521,10 +559,10 @@ export const createFieldJoin = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면 필드 조인 수정
|
||||
export const updateFieldJoin = async (req: Request, res: Response) => {
|
||||
export const updateFieldJoin = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const {
|
||||
layout_id, component_id, field_name,
|
||||
save_table, save_column, join_table, join_column, display_column,
|
||||
@@ -566,10 +604,10 @@ export const updateFieldJoin = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면 필드 조인 삭제
|
||||
export const deleteFieldJoin = async (req: Request, res: Response) => {
|
||||
export const deleteFieldJoin = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
let query = `DELETE FROM screen_field_joins WHERE id = $1`;
|
||||
const params: any[] = [id];
|
||||
@@ -600,9 +638,9 @@ export const deleteFieldJoin = async (req: Request, res: Response) => {
|
||||
// ============================================================
|
||||
|
||||
// 데이터 흐름 목록 조회
|
||||
export const getDataFlows = async (req: Request, res: Response) => {
|
||||
export const getDataFlows = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const { group_id, source_screen_id } = req.query;
|
||||
|
||||
let query = `
|
||||
@@ -650,10 +688,10 @@ export const getDataFlows = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 데이터 흐름 생성
|
||||
export const createDataFlow = async (req: Request, res: Response) => {
|
||||
export const createDataFlow = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const userId = (req.user as any).userId;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const userId = req.user?.userId || "";
|
||||
const {
|
||||
group_id, source_screen_id, source_action, target_screen_id, target_action,
|
||||
data_mapping, flow_type, flow_label, condition_expression, is_active
|
||||
@@ -689,10 +727,10 @@ export const createDataFlow = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 데이터 흐름 수정
|
||||
export const updateDataFlow = async (req: Request, res: Response) => {
|
||||
export const updateDataFlow = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const {
|
||||
group_id, source_screen_id, source_action, target_screen_id, target_action,
|
||||
data_mapping, flow_type, flow_label, condition_expression, is_active
|
||||
@@ -732,10 +770,10 @@ export const updateDataFlow = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 데이터 흐름 삭제
|
||||
export const deleteDataFlow = async (req: Request, res: Response) => {
|
||||
export const deleteDataFlow = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
let query = `DELETE FROM screen_data_flows WHERE id = $1`;
|
||||
const params: any[] = [id];
|
||||
@@ -766,9 +804,9 @@ export const deleteDataFlow = async (req: Request, res: Response) => {
|
||||
// ============================================================
|
||||
|
||||
// 화면-테이블 관계 목록 조회
|
||||
export const getTableRelations = async (req: Request, res: Response) => {
|
||||
export const getTableRelations = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const { screen_id, group_id } = req.query;
|
||||
|
||||
let query = `
|
||||
@@ -815,10 +853,10 @@ export const getTableRelations = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면-테이블 관계 생성
|
||||
export const createTableRelation = async (req: Request, res: Response) => {
|
||||
export const createTableRelation = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const userId = (req.user as any).userId;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const userId = req.user?.userId || "";
|
||||
const { group_id, screen_id, table_name, relation_type, crud_operations, description, is_active } = req.body;
|
||||
|
||||
if (!screen_id || !table_name) {
|
||||
@@ -848,10 +886,10 @@ export const createTableRelation = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면-테이블 관계 수정
|
||||
export const updateTableRelation = async (req: Request, res: Response) => {
|
||||
export const updateTableRelation = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
const { group_id, table_name, relation_type, crud_operations, description, is_active } = req.body;
|
||||
|
||||
let query = `
|
||||
@@ -883,10 +921,10 @@ export const updateTableRelation = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 화면-테이블 관계 삭제
|
||||
export const deleteTableRelation = async (req: Request, res: Response) => {
|
||||
export const deleteTableRelation = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const companyCode = (req.user as any).companyCode;
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
let query = `DELETE FROM screen_table_relations WHERE id = $1`;
|
||||
const params: any[] = [id];
|
||||
@@ -916,7 +954,7 @@ export const deleteTableRelation = async (req: Request, res: Response) => {
|
||||
// ============================================================
|
||||
|
||||
// 화면 레이아웃 요약 조회 (위젯 타입별 개수, 라벨 목록)
|
||||
export const getScreenLayoutSummary = async (req: Request, res: Response) => {
|
||||
export const getScreenLayoutSummary = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { screenId } = req.params;
|
||||
|
||||
@@ -984,7 +1022,7 @@ export const getScreenLayoutSummary = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 여러 화면의 레이아웃 요약 일괄 조회 (미니어처 렌더링용 좌표 포함)
|
||||
export const getMultipleScreenLayoutSummary = async (req: Request, res: Response) => {
|
||||
export const getMultipleScreenLayoutSummary = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { screenIds } = req.body;
|
||||
|
||||
@@ -1184,7 +1222,7 @@ export const getMultipleScreenLayoutSummary = async (req: Request, res: Response
|
||||
// ============================================================
|
||||
|
||||
// 여러 화면의 서브 테이블 정보 조회 (메인 테이블 → 서브 테이블 관계)
|
||||
export const getScreenSubTables = async (req: Request, res: Response) => {
|
||||
export const getScreenSubTables = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const { screenIds } = req.body;
|
||||
|
||||
@@ -2014,3 +2052,202 @@ export const getScreenSubTables = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 메뉴-화면그룹 동기화 API
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 화면관리 → 메뉴 동기화
|
||||
* screen_groups를 menu_info로 동기화
|
||||
*/
|
||||
export const syncScreenGroupsToMenuController = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const userCompanyCode = req.user?.companyCode || "*";
|
||||
const userId = req.user?.userId || "";
|
||||
const { targetCompanyCode } = req.body;
|
||||
|
||||
// 최고 관리자가 특정 회사를 지정한 경우 해당 회사로
|
||||
let companyCode = userCompanyCode;
|
||||
if (userCompanyCode === "*" && targetCompanyCode) {
|
||||
companyCode = targetCompanyCode;
|
||||
}
|
||||
|
||||
// 최고 관리자(*)는 회사를 지정해야 함
|
||||
if (companyCode === "*") {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "동기화할 회사를 선택해주세요.",
|
||||
});
|
||||
}
|
||||
|
||||
logger.info("화면관리 → 메뉴 동기화 요청", { companyCode, userId });
|
||||
|
||||
const result = await syncScreenGroupsToMenu(companyCode, userId);
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "동기화 중 오류가 발생했습니다.",
|
||||
errors: result.errors,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `동기화 완료: 생성 ${result.created}개, 연결 ${result.linked}개, 스킵 ${result.skipped}개`,
|
||||
data: result,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("화면관리 → 메뉴 동기화 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "동기화에 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 메뉴 → 화면관리 동기화
|
||||
* menu_info를 screen_groups로 동기화
|
||||
*/
|
||||
export const syncMenuToScreenGroupsController = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const userCompanyCode = req.user?.companyCode || "*";
|
||||
const userId = req.user?.userId || "";
|
||||
const { targetCompanyCode } = req.body;
|
||||
|
||||
// 최고 관리자가 특정 회사를 지정한 경우 해당 회사로
|
||||
let companyCode = userCompanyCode;
|
||||
if (userCompanyCode === "*" && targetCompanyCode) {
|
||||
companyCode = targetCompanyCode;
|
||||
}
|
||||
|
||||
// 최고 관리자(*)는 회사를 지정해야 함
|
||||
if (companyCode === "*") {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "동기화할 회사를 선택해주세요.",
|
||||
});
|
||||
}
|
||||
|
||||
logger.info("메뉴 → 화면관리 동기화 요청", { companyCode, userId });
|
||||
|
||||
const result = await syncMenuToScreenGroups(companyCode, userId);
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "동기화 중 오류가 발생했습니다.",
|
||||
errors: result.errors,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `동기화 완료: 생성 ${result.created}개, 연결 ${result.linked}개, 스킵 ${result.skipped}개`,
|
||||
data: result,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("메뉴 → 화면관리 동기화 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "동기화에 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 동기화 상태 조회
|
||||
*/
|
||||
export const getSyncStatusController = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const userCompanyCode = req.user?.companyCode || "*";
|
||||
const { targetCompanyCode } = req.query;
|
||||
|
||||
// 최고 관리자가 특정 회사를 지정한 경우 해당 회사로
|
||||
let companyCode = userCompanyCode;
|
||||
if (userCompanyCode === "*" && targetCompanyCode) {
|
||||
companyCode = targetCompanyCode as string;
|
||||
}
|
||||
|
||||
// 최고 관리자(*)는 회사를 지정해야 함
|
||||
if (companyCode === "*") {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "조회할 회사를 선택해주세요.",
|
||||
});
|
||||
}
|
||||
|
||||
const status = await getSyncStatus(companyCode);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: status,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("동기화 상태 조회 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "동기화 상태 조회에 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 전체 회사 동기화
|
||||
* 모든 회사에 대해 양방향 동기화 수행 (최고 관리자만)
|
||||
*/
|
||||
export const syncAllCompaniesController = async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const userCompanyCode = req.user?.companyCode || "*";
|
||||
const userId = req.user?.userId || "";
|
||||
|
||||
// 최고 관리자만 전체 동기화 가능
|
||||
if (userCompanyCode !== "*") {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: "전체 동기화는 최고 관리자만 수행할 수 있습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
logger.info("전체 회사 동기화 요청", { userId });
|
||||
|
||||
const result = await syncAllCompanies(userId);
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "전체 동기화 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
// 결과 요약
|
||||
const totalCreated = result.results.reduce((sum, r) => sum + r.created, 0);
|
||||
const totalLinked = result.results.reduce((sum, r) => sum + r.linked, 0);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `전체 동기화 완료: ${result.totalCompanies}개 회사 중 ${result.successCount}개 성공`,
|
||||
data: {
|
||||
totalCompanies: result.totalCompanies,
|
||||
successCount: result.successCount,
|
||||
failedCount: result.failedCount,
|
||||
totalCreated,
|
||||
totalLinked,
|
||||
details: result.results,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("전체 회사 동기화 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "전체 동기화에 실패했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user