diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index 7b33e0f9..e0641580 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -315,14 +315,17 @@ export class ScreenManagementService { }>; }> { // 권한 확인 - const targetScreen = await prisma.screen_definitions.findUnique({ - where: { screen_id: screenId }, - }); + const targetScreens = await query<{ company_code: string | null }>( + `SELECT company_code FROM screen_definitions WHERE screen_id = $1 LIMIT 1`, + [screenId] + ); - if (!targetScreen) { + if (targetScreens.length === 0) { throw new Error("화면을 찾을 수 없습니다."); } + const targetScreen = targetScreens[0]; + if ( userCompanyCode !== "*" && targetScreen.company_code !== "*" && @@ -332,19 +335,27 @@ export class ScreenManagementService { } // 같은 회사의 모든 활성 화면에서 이 화면을 참조하는지 확인 - const whereClause = { - is_active: { not: "D" }, - ...(userCompanyCode !== "*" && { - company_code: { in: [userCompanyCode, "*"] }, - }), - }; + const whereConditions: string[] = ["sd.is_active != 'D'"]; + const params: any[] = []; - const allScreens = await prisma.screen_definitions.findMany({ - where: whereClause, - include: { - layouts: true, - }, - }); + if (userCompanyCode !== "*") { + whereConditions.push(`sd.company_code IN ($${params.length + 1}, $${params.length + 2})`); + params.push(userCompanyCode, "*"); + } + + const whereSQL = whereConditions.join(" AND "); + + // 화면과 레이아웃을 JOIN해서 조회 + const allScreens = await query( + `SELECT + sd.screen_id, sd.screen_name, sd.screen_code, sd.company_code, + sl.layout_id, sl.component_id, sl.component_type, sl.properties + FROM screen_definitions sd + LEFT JOIN screen_layouts sl ON sd.screen_id = sl.screen_id + WHERE ${whereSQL} + ORDER BY sd.screen_id, sl.layout_id`, + params + ); const dependencies: Array<{ screenId: number; @@ -660,44 +671,50 @@ export class ScreenManagementService { } /** - * 휴지통 화면들의 메뉴 할당 정리 (관리자용) + * 휴지통 화면들의 메뉴 할당 정리 (관리자용) (✅ Raw Query 전환 완료) */ async cleanupDeletedScreenMenuAssignments(): Promise<{ updatedCount: number; message: string; }> { - const result = await prisma.$executeRaw` - UPDATE screen_menu_assignments - SET is_active = 'N' - WHERE screen_id IN ( - SELECT screen_id - FROM screen_definitions - WHERE is_active = 'D' - ) AND is_active = 'Y' - `; + const result = await query( + `UPDATE screen_menu_assignments + SET is_active = 'N' + WHERE screen_id IN ( + SELECT screen_id + FROM screen_definitions + WHERE is_active = 'D' + ) AND is_active = 'Y'`, + [] + ); + + const updatedCount = result.length; return { - updatedCount: Number(result), - message: `${result}개의 메뉴 할당이 정리되었습니다.`, + updatedCount, + message: `${updatedCount}개의 메뉴 할당이 정리되었습니다.`, }; } /** - * 화면 영구 삭제 (휴지통에서 완전 삭제) + * 화면 영구 삭제 (휴지통에서 완전 삭제) (✅ Raw Query 전환 완료) */ async permanentDeleteScreen( screenId: number, userCompanyCode: string ): Promise { // 권한 확인 - const existingScreen = await prisma.screen_definitions.findUnique({ - where: { screen_id: screenId }, - }); + const screens = await query<{ company_code: string | null; is_active: string }>( + `SELECT company_code, is_active FROM screen_definitions WHERE screen_id = $1 LIMIT 1`, + [screenId] + ); - if (!existingScreen) { + if (screens.length === 0) { throw new Error("화면을 찾을 수 없습니다."); } + const existingScreen = screens[0]; + if ( userCompanyCode !== "*" && existingScreen.company_code !== userCompanyCode @@ -710,14 +727,16 @@ export class ScreenManagementService { throw new Error("휴지통에 있는 화면만 영구 삭제할 수 있습니다."); } - // 물리적 삭제 (CASCADE로 관련 레이아웃과 메뉴 할당도 함께 삭제됨) - await prisma.screen_definitions.delete({ - where: { screen_id: screenId }, + // 물리적 삭제 (수동으로 관련 데이터 삭제) + await transaction(async (client) => { + await client.query(`DELETE FROM screen_layouts WHERE screen_id = $1`, [screenId]); + await client.query(`DELETE FROM screen_menu_assignments WHERE screen_id = $1`, [screenId]); + await client.query(`DELETE FROM screen_definitions WHERE screen_id = $1`, [screenId]); }); } /** - * 휴지통 화면 목록 조회 + * 휴지통 화면 목록 조회 (✅ Raw Query 전환 완료) */ async getDeletedScreens( companyCode: string, @@ -732,37 +751,54 @@ export class ScreenManagementService { } > > { - const whereClause: any = { is_active: "D" }; + const offset = (page - 1) * size; + const whereConditions: string[] = ["is_active = 'D'"]; + const params: any[] = []; if (companyCode !== "*") { - whereClause.company_code = companyCode; + whereConditions.push(`company_code = $${params.length + 1}`); + params.push(companyCode); } - const [screens, total] = await Promise.all([ - prisma.screen_definitions.findMany({ - where: whereClause, - skip: (page - 1) * size, - take: size, - orderBy: { deleted_date: "desc" }, - }), - prisma.screen_definitions.count({ where: whereClause }), + const whereSQL = whereConditions.join(" AND "); + + const [screens, totalResult] = await Promise.all([ + query( + `SELECT * FROM screen_definitions + WHERE ${whereSQL} + ORDER BY deleted_date DESC NULLS LAST + LIMIT $${params.length + 1} OFFSET $${params.length + 2}`, + [...params, size, offset] + ), + query<{ count: string }>( + `SELECT COUNT(*)::text as count FROM screen_definitions WHERE ${whereSQL}`, + params + ), ]); + const total = parseInt(totalResult[0]?.count || "0", 10); + // 테이블 라벨 정보를 한 번에 조회 const tableNames = [ - ...new Set(screens.map((s) => s.table_name).filter(Boolean)), + ...new Set(screens.map((s: any) => s.table_name).filter(Boolean)), ]; - const tableLabels = await prisma.table_labels.findMany({ - where: { table_name: { in: tableNames } }, - select: { table_name: true, table_label: true }, - }); - const tableLabelMap = new Map( - tableLabels.map((tl) => [tl.table_name, tl.table_label || tl.table_name]) - ); + let tableLabelMap = new Map(); + + if (tableNames.length > 0) { + const placeholders = tableNames.map((_, i) => `$${i + 1}`).join(", "); + const tableLabels = await query<{ table_name: string; table_label: string | null }>( + `SELECT table_name, table_label FROM table_labels WHERE table_name IN (${placeholders})`, + tableNames + ); + + tableLabelMap = new Map( + tableLabels.map((tl: any) => [tl.table_name, tl.table_label || tl.table_name]) + ); + } return { - data: screens.map((screen) => ({ + data: screens.map((screen: any) => ({ ...this.mapToScreenDefinition(screen, tableLabelMap), deletedDate: screen.deleted_date || undefined, deletedBy: screen.deleted_by || undefined,