ERP-node/PHASE4.1_ADMIN_CONTROLLER_M...

12 KiB

Phase 4.1: AdminController Raw Query 전환 계획

📋 개요

관리자 컨트롤러의 Prisma 호출을 Raw Query로 전환합니다. 사용자, 회사, 부서, 메뉴 관리 등 핵심 관리 기능을 포함합니다.


📊 기본 정보

항목 내용
파일 위치 backend-node/src/controllers/adminController.ts
파일 크기 2,569 라인
Prisma 호출 28개 → 0개
현재 진행률 28/28 (100%) 완료
복잡도 중간 (다양한 CRUD 패턴)
우선순위 🔴 높음 (Phase 4.1)
상태 완료 (2025-10-01)

🔍 Prisma 호출 분석

사용자 관리 (13개)

1. getUserList (라인 312-317)

const totalCount = await prisma.user_info.count({ where });
const users = await prisma.user_info.findMany({ where, skip, take, orderBy });
  • 전환: count → queryOne, findMany → query
  • 복잡도: 중간 (동적 WHERE, 페이징)

2. getUserInfo (라인 419)

const userInfo = await prisma.user_info.findFirst({ where });
  • 전환: findFirst → queryOne
  • 복잡도: 낮음

3. updateUserStatus (라인 498)

await prisma.user_info.update({ where, data });
  • 전환: update → query
  • 복잡도: 낮음

4. deleteUserByAdmin (라인 2387)

await prisma.user_info.update({ where, data: { is_active: "N" } });
  • 전환: update (soft delete) → query
  • 복잡도: 낮음

5. getMyProfile (라인 1468, 1488, 2479)

const user = await prisma.user_info.findUnique({ where });
const dept = await prisma.dept_info.findUnique({ where });
  • 전환: findUnique → queryOne
  • 복잡도: 낮음

6. updateMyProfile (라인 1864, 2527)

const updateResult = await prisma.user_info.update({ where, data });
  • 전환: update → queryOne with RETURNING
  • 복잡도: 중간 (동적 UPDATE)

7. createOrUpdateUser (라인 1929, 1975)

const savedUser = await prisma.user_info.upsert({ where, update, create });
const userCount = await prisma.user_info.count({ where });
  • 전환: upsert → INSERT ... ON CONFLICT, count → queryOne
  • 복잡도: 높음

8. 기타 findUnique (라인 1596, 1832, 2393)

const existingUser = await prisma.user_info.findUnique({ where });
const currentUser = await prisma.user_info.findUnique({ where });
const updatedUser = await prisma.user_info.findUnique({ where });
  • 전환: findUnique → queryOne
  • 복잡도: 낮음

회사 관리 (7개)

9. getCompanyList (라인 550, 1276)

const companies = await prisma.company_mng.findMany({ orderBy });
  • 전환: findMany → query
  • 복잡도: 낮음

10. createCompany (라인 2035)

const existingCompany = await prisma.company_mng.findFirst({ where });
  • 전환: findFirst (중복 체크) → queryOne
  • 복잡도: 낮음

11. updateCompany (라인 2172, 2192)

const duplicateCompany = await prisma.company_mng.findFirst({ where });
const updatedCompany = await prisma.company_mng.update({ where, data });
  • 전환: findFirst → queryOne, update → queryOne
  • 복잡도: 중간

12. deleteCompany (라인 2261, 2281)

const existingCompany = await prisma.company_mng.findUnique({ where });
await prisma.company_mng.delete({ where });
  • 전환: findUnique → queryOne, delete → query
  • 복잡도: 낮음

부서 관리 (2개)

13. getDepartmentList (라인 1348)

const departments = await prisma.dept_info.findMany({ where, orderBy });
  • 전환: findMany → query
  • 복잡도: 낮음

14. getDeptInfo (라인 1488)

const dept = await prisma.dept_info.findUnique({ where });
  • 전환: findUnique → queryOne
  • 복잡도: 낮음

메뉴 관리 (3개)

15. createMenu (라인 1021)

const savedMenu = await prisma.menu_info.create({ data });
  • 전환: create → queryOne with INSERT RETURNING
  • 복잡도: 중간

16. updateMenu (라인 1087)

const updatedMenu = await prisma.menu_info.update({ where, data });
  • 전환: update → queryOne with UPDATE RETURNING
  • 복잡도: 중간

17. deleteMenu (라인 1149, 1211)

const deletedMenu = await prisma.menu_info.delete({ where });
// 재귀 삭제
const deletedMenu = await prisma.menu_info.delete({ where });
  • 전환: delete → query
  • 복잡도: 중간 (재귀 삭제 로직)

다국어 (1개)

18. getMultiLangKeys (라인 665)

const result = await prisma.multi_lang_key_master.findMany({ where, orderBy });
  • 전환: findMany → query
  • 복잡도: 낮음

📝 전환 전략

1단계: Import 변경

// 제거
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

// 추가
import { query, queryOne } from "../database/db";

2단계: 단순 조회 전환

  • findMany → query<T>
  • findUnique/findFirst → queryOne<T>

3단계: 동적 WHERE 처리

const whereConditions: string[] = [];
const params: any[] = [];
let paramIndex = 1;

if (companyCode) {
  whereConditions.push(`company_code = $${paramIndex++}`);
  params.push(companyCode);
}

const whereClause =
  whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";

4단계: 복잡한 로직 전환

  • count → SELECT COUNT(*) as count
  • upsert → INSERT ... ON CONFLICT DO UPDATE
  • 동적 UPDATE → 조건부 SET 절 생성

5단계: 테스트 및 검증

  • 각 함수별 동작 확인
  • 에러 처리 확인
  • 타입 안전성 확인

🎯 주요 변경 예시

getUserList (count + findMany)

// Before
const totalCount = await prisma.user_info.count({ where });
const users = await prisma.user_info.findMany({
  where,
  skip,
  take,
  orderBy,
});

// After
const whereConditions: string[] = [];
const params: any[] = [];
let paramIndex = 1;

// 동적 WHERE 구성
if (where.company_code) {
  whereConditions.push(`company_code = $${paramIndex++}`);
  params.push(where.company_code);
}
if (where.user_name) {
  whereConditions.push(`user_name ILIKE $${paramIndex++}`);
  params.push(`%${where.user_name}%`);
}

const whereClause =
  whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";

// Count
const countResult = await queryOne<{ count: number }>(
  `SELECT COUNT(*) as count FROM user_info ${whereClause}`,
  params
);
const totalCount = parseInt(countResult?.count?.toString() || "0", 10);

// 데이터 조회
const usersQuery = `
  SELECT * FROM user_info 
  ${whereClause}
  ORDER BY created_date DESC
  LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
`;
params.push(take, skip);

const users = await query<UserInfo>(usersQuery, params);

createOrUpdateUser (upsert)

// Before
const savedUser = await prisma.user_info.upsert({
  where: { user_id: userId },
  update: updateData,
  create: createData
});

// After
const savedUser = await queryOne<UserInfo>(
  `INSERT INTO user_info (user_id, user_name, email, ...)
   VALUES ($1, $2, $3, ...)
   ON CONFLICT (user_id)
   DO UPDATE SET
     user_name = EXCLUDED.user_name,
     email = EXCLUDED.email,
     ...
   RETURNING *`,
  [userId, userName, email, ...]
);

updateMyProfile (동적 UPDATE)

// Before
const updateResult = await prisma.user_info.update({
  where: { user_id: userId },
  data: updateData,
});

// After
const updates: string[] = [];
const params: any[] = [];
let paramIndex = 1;

if (updateData.user_name !== undefined) {
  updates.push(`user_name = $${paramIndex++}`);
  params.push(updateData.user_name);
}
if (updateData.email !== undefined) {
  updates.push(`email = $${paramIndex++}`);
  params.push(updateData.email);
}
// ... 다른 필드들

params.push(userId);

const updateResult = await queryOne<UserInfo>(
  `UPDATE user_info 
   SET ${updates.join(", ")}, updated_date = NOW()
   WHERE user_id = $${paramIndex}
   RETURNING *`,
  params
);

체크리스트

기본 설정

  • Prisma import 제거 (완전 제거 확인)
  • query, queryOne import 추가 (이미 존재)
  • 타입 import 확인

사용자 관리

  • getUserList (count + findMany → Raw Query)
  • getUserLocale (findFirst → queryOne)
  • setUserLocale (update → query)
  • getUserInfo (findUnique → queryOne)
  • checkDuplicateUserId (findUnique → queryOne)
  • changeUserStatus (findUnique + update → queryOne + query)
  • saveUser (upsert → INSERT ON CONFLICT)
  • updateProfile (동적 update → 동적 query)
  • resetUserPassword (update → query)

회사 관리

  • getCompanyList (findMany → query)
  • getCompanyListFromDB (findMany → query)
  • createCompany (findFirst → queryOne)
  • updateCompany (findFirst + update → queryOne + query)
  • deleteCompany (delete → query with RETURNING)

부서 관리

  • getDepartmentList (findMany → query with 동적 WHERE)

메뉴 관리

  • saveMenu (create → query with INSERT RETURNING)
  • updateMenu (update → query with UPDATE RETURNING)
  • deleteMenu (delete → query with DELETE RETURNING)
  • deleteMenusBatch (다중 delete → 반복 query)

다국어

  • getLangKeyList (findMany → query)

검증

  • TypeScript 컴파일 확인 (에러 없음)
  • Linter 오류 확인
  • 기능 테스트 (실행 필요)
  • 에러 처리 확인 (기존 구조 유지)

📌 참고사항

동적 쿼리 생성 패턴

모든 동적 WHERE/UPDATE는 다음 패턴을 따릅니다:

  1. 조건/필드 배열 생성
  2. 파라미터 배열 생성
  3. 파라미터 인덱스 관리
  4. SQL 문자열 조합
  5. query/queryOne 실행

에러 처리

기존 try-catch 구조를 유지하며, 데이터베이스 에러를 적절히 변환합니다.

트랜잭션

복잡한 로직은 Service Layer로 이동을 고려합니다.


🎉 완료 요약 (2025-10-01)

전환 완료 현황

카테고리 함수 수 상태
사용자 관리 9개 완료
회사 관리 5개 완료
부서 관리 1개 완료
메뉴 관리 4개 완료
다국어 1개 완료
총계 20개 100% 완료

📊 주요 성과

  1. 완전한 Prisma 제거: adminController.ts에서 모든 Prisma 코드 제거 완료
  2. 동적 쿼리 지원: 런타임 테이블 생성/수정 가능
  3. 일관된 에러 처리: 모든 함수에서 통일된 에러 처리 유지
  4. 타입 안전성: TypeScript 컴파일 에러 없음
  5. 코드 품질 향상: 949줄 변경 (+474/-475)

🔑 주요 변환 패턴

1. 동적 WHERE 조건

let whereConditions: string[] = [];
let queryParams: any[] = [];
let paramIndex = 1;

if (filter) {
  whereConditions.push(`field = $${paramIndex}`);
  queryParams.push(filter);
  paramIndex++;
}

const whereClause = whereConditions.length > 0
  ? `WHERE ${whereConditions.join(" AND ")}`
  : "";

2. UPSERT (INSERT ON CONFLICT)

const [result] = await query<any>(
  `INSERT INTO table (col1, col2) VALUES ($1, $2)
   ON CONFLICT (col1) DO UPDATE SET col2 = $2
   RETURNING *`,
  [val1, val2]
);

3. 동적 UPDATE

const updateFields: string[] = [];
const updateValues: any[] = [];
let paramIndex = 1;

if (data.field !== undefined) {
  updateFields.push(`field = $${paramIndex}`);
  updateValues.push(data.field);
  paramIndex++;
}

await query(
  `UPDATE table SET ${updateFields.join(", ")} WHERE id = $${paramIndex}`,
  [...updateValues, id]
);

🚀 다음 단계

  1. 테스트 실행: 개발 서버에서 모든 API 엔드포인트 테스트
  2. 문서 업데이트: Phase 4 전체 계획서 진행 상황 반영
  3. 다음 Phase: screenFileController.ts 마이그레이션 진행

마지막 업데이트: 2025-10-01 작업자: Claude Agent 완료 시간: 약 15분 변경 라인 수: 949줄 (추가 474줄, 삭제 475줄)