# 📋 파일별 상세 Prisma → Raw Query 마이그레이션 계획

## 🎯 개요

총 42개 파일, 444개 Prisma 호출을 Raw Query로 전환하는 상세 계획입니다.
각 파일의 복잡도, 의존성, 전환 전략을 분석하여 기능 손실 없는 완전한 마이그레이션을 수행합니다.

---

## 🔴 Phase 2: 핵심 서비스 전환 (107개 호출)

### 1. screenManagementService.ts (46개 호출) ⭐ 최우선

#### 📊 현재 상태 분석

- **복잡도**: 매우 높음 (JSON 처리, 복잡한 조인, 트랜잭션)
- **주요 기능**: 화면 정의 관리, 레이아웃 저장/불러오기, 메뉴 할당
- **의존성**: screen_definitions, screen_components, screen_layouts 등

#### 🔧 전환 전략

##### 1.1 기본 CRUD 작업 전환

```typescript
// 기존 Prisma 코드
const screen = await prisma.screen_definitions.create({
  data: {
    screen_name: screenData.screenName,
    screen_code: screenData.screenCode,
    table_name: screenData.tableName,
    company_code: screenData.companyCode,
    description: screenData.description,
    created_by: screenData.createdBy,
  },
});

// 새로운 Raw Query 코드
const { query, params } = QueryBuilder.insert(
  "screen_definitions",
  {
    screen_name: screenData.screenName,
    screen_code: screenData.screenCode,
    table_name: screenData.tableName,
    company_code: screenData.companyCode,
    description: screenData.description,
    created_by: screenData.createdBy,
    created_at: new Date(),
    updated_at: new Date(),
  },
  {
    returning: ["*"],
  }
);
const [screen] = await DatabaseManager.query(query, params);
```

##### 1.2 복잡한 조인 쿼리 전환

```typescript
// 기존 Prisma 코드 (복잡한 include)
const screens = await prisma.screen_definitions.findMany({
  where: whereClause,
  include: {
    screen_components: true,
    screen_layouts: true,
  },
  orderBy: { created_at: "desc" },
  skip: (page - 1) * size,
  take: size,
});

// 새로운 Raw Query 코드
const { query, params } = QueryBuilder.select("screen_definitions", {
  columns: [
    "sd.*",
    "json_agg(DISTINCT sc.*) as screen_components",
    "json_agg(DISTINCT sl.*) as screen_layouts",
  ],
  joins: [
    {
      type: "LEFT",
      table: "screen_components sc",
      on: "sd.id = sc.screen_id",
    },
    {
      type: "LEFT",
      table: "screen_layouts sl",
      on: "sd.id = sl.screen_id",
    },
  ],
  where: whereClause,
  orderBy: "sd.created_at DESC",
  limit: size,
  offset: (page - 1) * size,
  groupBy: ["sd.id"],
});
const screens = await DatabaseManager.query(query, params);
```

##### 1.3 JSON 데이터 처리 전환

```typescript
// 기존 Prisma 코드 (JSON 검색)
const screens = await prisma.screen_definitions.findMany({
  where: {
    screen_config: { path: ["type"], equals: "form" },
  },
});

// 새로운 Raw Query 코드
const screens = await DatabaseManager.query(
  `
  SELECT * FROM screen_definitions 
  WHERE screen_config->>'type' = $1
`,
  ["form"]
);
```

##### 1.4 트랜잭션 처리 전환

```typescript
// 기존 Prisma 트랜잭션
await prisma.$transaction(async (tx) => {
  const screen = await tx.screen_definitions.create({ data: screenData });
  await tx.screen_components.createMany({ data: components });
  return screen;
});

// 새로운 Raw Query 트랜잭션
await DatabaseManager.transaction(async (client) => {
  const screenResult = await client.query(
    "INSERT INTO screen_definitions (...) VALUES (...) RETURNING *",
    screenParams
  );
  const screen = screenResult.rows[0];

  for (const component of components) {
    await client.query(
      "INSERT INTO screen_components (...) VALUES (...)",
      componentParams
    );
  }

  return screen;
});
```

#### 🧪 테스트 전략

1. **단위 테스트**: 각 메서드별 입출력 검증
2. **통합 테스트**: 화면 생성 → 조회 → 수정 → 삭제 전체 플로우
3. **JSON 데이터 테스트**: 복잡한 레이아웃 데이터 저장/복원
4. **성능 테스트**: 대량 화면 데이터 처리 성능 비교

#### ⚠️ 주의사항

- JSON 데이터 타입 변환 주의 (PostgreSQL JSONB ↔ JavaScript Object)
- 날짜 타입 변환 (Prisma DateTime ↔ PostgreSQL timestamp)
- NULL 값 처리 일관성 유지

---

### 2. tableManagementService.ts (35개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 매우 높음 (메타데이터 조회, DDL 실행, 동적 쿼리)
- **주요 기능**: 테이블/컬럼 정보 관리, 동적 테이블 생성, 메타데이터 캐싱
- **의존성**: information_schema, table_labels, column_labels

#### 🔧 전환 전략

##### 2.1 메타데이터 조회 (이미 Raw Query 사용 중)

```typescript
// 현재 코드 (이미 Raw Query)
const rawTables = await prisma.$queryRaw<any[]>`
  SELECT 
    t.table_name as "tableName",
    COALESCE(tl.table_label, t.table_name) as "displayName"
  FROM information_schema.tables t
  LEFT JOIN table_labels tl ON t.table_name = tl.table_name
  WHERE t.table_schema = 'public'
`;

// 새로운 Raw Query 코드 (Prisma 제거)
const rawTables = await DatabaseManager.query(`
  SELECT 
    t.table_name as "tableName",
    COALESCE(tl.table_label, t.table_name) as "displayName"
  FROM information_schema.tables t
  LEFT JOIN table_labels tl ON t.table_name = tl.table_name
  WHERE t.table_schema = 'public'
`);
```

##### 2.2 동적 테이블 데이터 조회

```typescript
// 기존 Prisma 코드 (동적 테이블명)
const data = await prisma.$queryRawUnsafe(
  `SELECT * FROM ${tableName} WHERE id = $1`,
  [id]
);

// 새로운 Raw Query 코드
const data = await DatabaseManager.queryUnsafe(
  `SELECT * FROM ${DatabaseValidator.sanitizeTableName(
    tableName
  )} WHERE id = $1`,
  [id]
);
```

##### 2.3 UPSERT 작업 전환

```typescript
// 기존 Prisma UPSERT
await prisma.table_labels.upsert({
  where: { table_name: tableName },
  update: { table_label: label, updated_date: new Date() },
  create: {
    table_name: tableName,
    table_label: label,
    created_date: new Date(),
  },
});

// 새로운 Raw Query UPSERT
const { query, params } = QueryBuilder.insert(
  "table_labels",
  {
    table_name: tableName,
    table_label: label,
    created_date: new Date(),
    updated_date: new Date(),
  },
  {
    onConflict: {
      columns: ["table_name"],
      action: "DO UPDATE",
      updateSet: ["table_label", "updated_date"],
    },
    returning: ["*"],
  }
);
await DatabaseManager.query(query, params);
```

#### 🧪 테스트 전략

1. **메타데이터 테스트**: information_schema 조회 결과 일치성
2. **동적 쿼리 테스트**: 다양한 테이블명/컬럼명으로 안전성 검증
3. **DDL 테스트**: 테이블 생성/수정/삭제 기능
4. **캐시 테스트**: 메타데이터 캐싱 동작 검증

---

### 3. dataflowService.ts (31개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 높음 (관계 관리, 복잡한 비즈니스 로직)
- **주요 기능**: 테이블 관계 관리, 데이터플로우 다이어그램
- **의존성**: table_relationships, dataflow_diagrams

#### 🔧 전환 전략

##### 3.1 관계 생성 로직

```typescript
// 기존 Prisma 코드
const maxDiagramId = await prisma.table_relationships.findFirst({
  where: { company_code: data.companyCode },
  orderBy: { diagram_id: "desc" },
  select: { diagram_id: true },
});

const relationship = await prisma.table_relationships.create({
  data: {
    diagram_id: diagramId,
    relationship_name: data.relationshipName,
    // ... 기타 필드
  },
});

// 새로운 Raw Query 코드
const maxDiagramResult = await DatabaseManager.query(
  `
  SELECT diagram_id FROM table_relationships 
  WHERE company_code = $1 
  ORDER BY diagram_id DESC 
  LIMIT 1
`,
  [data.companyCode]
);

const diagramId = (maxDiagramResult[0]?.diagram_id || 0) + 1;

const { query, params } = QueryBuilder.insert(
  "table_relationships",
  {
    diagram_id: diagramId,
    relationship_name: data.relationshipName,
    // ... 기타 필드
  },
  { returning: ["*"] }
);

const [relationship] = await DatabaseManager.query(query, params);
```

#### 🧪 테스트 전략

1. **관계 생성 테스트**: 다양한 테이블 관계 패턴
2. **중복 검증 테스트**: 동일 관계 생성 방지
3. **다이어그램 테스트**: 복잡한 관계도 생성/조회

---

## 🟡 Phase 3: 관리 기능 전환 (162개 호출)

### 4. multilangService.ts (25개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 높음 (재귀 쿼리, 다국어 처리)
- **주요 기능**: 다국어 번역 관리, 계층 구조 처리
- **의존성**: multilang_translations, 재귀 관계

#### 🔧 전환 전략

##### 4.1 재귀 쿼리 전환

```typescript
// 기존 Prisma 코드 (재귀 관계 조회)
const translations = await prisma.multilang_translations.findMany({
  where: { parent_id: null },
  include: {
    children: {
      include: {
        children: true, // 중첩 include
      },
    },
  },
});

// 새로운 Raw Query 코드 (WITH RECURSIVE)
const translations = await DatabaseManager.query(`
  WITH RECURSIVE translation_tree AS (
    SELECT *, 0 as level 
    FROM multilang_translations 
    WHERE parent_id IS NULL
    
    UNION ALL
    
    SELECT t.*, tt.level + 1
    FROM multilang_translations t
    JOIN translation_tree tt ON t.parent_id = tt.id
  )
  SELECT * FROM translation_tree 
  ORDER BY level, sort_order
`);
```

#### 🧪 테스트 전략

1. **재귀 쿼리 테스트**: 깊은 계층 구조 처리
2. **다국어 테스트**: 다양한 언어 코드 처리
3. **성능 테스트**: 대량 번역 데이터 처리

---

### 5. batchService.ts (16개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 중간 (배치 작업 관리)
- **주요 기능**: 배치 작업 스케줄링, 실행 이력 관리
- **의존성**: batch_configs, batch_execution_logs

#### 🔧 전환 전략

##### 5.1 배치 설정 관리

```typescript
// 기존 Prisma 코드
const batchConfigs = await prisma.batch_configs.findMany({
  where: { is_active: true },
  include: { execution_logs: { take: 10, orderBy: { created_at: "desc" } } },
});

// 새로운 Raw Query 코드
const batchConfigs = await DatabaseManager.query(`
  SELECT 
    bc.*,
    json_agg(
      json_build_object(
        'id', bel.id,
        'status', bel.status,
        'created_at', bel.created_at
      ) ORDER BY bel.created_at DESC
    ) FILTER (WHERE bel.id IS NOT NULL) as execution_logs
  FROM batch_configs bc
  LEFT JOIN (
    SELECT DISTINCT ON (batch_config_id) 
      batch_config_id, id, status, created_at,
      ROW_NUMBER() OVER (PARTITION BY batch_config_id ORDER BY created_at DESC) as rn
    FROM batch_execution_logs
  ) bel ON bc.id = bel.batch_config_id AND bel.rn <= 10
  WHERE bc.is_active = true
  GROUP BY bc.id
`);
```

---

### 7. dynamicFormService.ts (15개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 높음 (UPSERT, 동적 테이블 처리, 타입 변환)
- **주요 기능**: 동적 폼 데이터 저장/조회, 데이터 검증, 타입 변환
- **의존성**: 동적 테이블들, form_data, 이벤트 트리거

#### 🔧 전환 전략

##### 7.1 동적 UPSERT 로직 전환

```typescript
// 기존 Prisma 코드 (동적 테이블 UPSERT)
const existingRecord = await prisma.$queryRawUnsafe(
  `SELECT * FROM ${tableName} WHERE id = $1`,
  [id]
);

if (existingRecord.length > 0) {
  await prisma.$executeRawUnsafe(updateQuery, updateValues);
} else {
  await prisma.$executeRawUnsafe(insertQuery, insertValues);
}

// 새로운 Raw Query 코드 (PostgreSQL UPSERT)
const upsertQuery = `
  INSERT INTO ${DatabaseValidator.sanitizeTableName(tableName)} 
  (${columns.join(", ")}) 
  VALUES (${placeholders.join(", ")})
  ON CONFLICT (id) 
  DO UPDATE SET 
    ${updateColumns.map((col) => `${col} = EXCLUDED.${col}`).join(", ")},
    updated_at = NOW()
  RETURNING *
`;

const [result] = await DatabaseManager.query(upsertQuery, values);
```

##### 7.2 타입 변환 로직 강화

```typescript
// 기존 타입 변환 (Prisma 자동 처리)
const data = await prisma.someTable.create({ data: formData });

// 새로운 타입 변환 (명시적 처리)
const convertedData = this.convertFormDataForPostgreSQL(formData, tableSchema);
const { query, params } = QueryBuilder.insert(tableName, convertedData, {
  returning: ["*"]
});
const [result] = await DatabaseManager.query(query, params);

// 타입 변환 함수 강화
private convertFormDataForPostgreSQL(data: any, schema: TableColumn[]): any {
  const converted = {};

  for (const [key, value] of Object.entries(data)) {
    const column = schema.find(col => col.columnName === key);
    if (column) {
      converted[key] = this.convertValueByType(value, column.dataType);
    }
  }

  return converted;
}
```

##### 7.3 동적 검증 로직

```typescript
// 기존 Prisma 검증 (스키마 기반)
const validation = await prisma.$validator.validate(data, schema);

// 새로운 Raw Query 검증
async validateFormData(data: any, tableName: string): Promise<ValidationResult> {
  // 테이블 스키마 조회
  const schema = await this.getTableSchema(tableName);

  const errors: ValidationError[] = [];

  for (const column of schema) {
    const value = data[column.columnName];

    // NULL 검증
    if (!column.nullable && (value === null || value === undefined)) {
      errors.push({
        field: column.columnName,
        message: `${column.columnName}은(는) 필수 입력 항목입니다.`,
        code: 'REQUIRED_FIELD'
      });
    }

    // 타입 검증
    if (value !== null && !this.isValidType(value, column.dataType)) {
      errors.push({
        field: column.columnName,
        message: `${column.columnName}의 데이터 타입이 올바르지 않습니다.`,
        code: 'INVALID_TYPE'
      });
    }
  }

  return { valid: errors.length === 0, errors };
}
```

#### 🧪 테스트 전략

1. **UPSERT 테스트**: 신규 생성 vs 기존 업데이트 시나리오
2. **타입 변환 테스트**: 다양한 PostgreSQL 타입 변환
3. **검증 테스트**: 필수 필드, 타입 검증, 길이 제한
4. **동적 테이블 테스트**: 런타임에 생성된 테이블 처리

---

### 8. externalDbConnectionService.ts (15개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 높음 (다중 DB 연결, 외부 시스템 연동)
- **주요 기능**: 외부 데이터베이스 연결 관리, 스키마 동기화
- **의존성**: external_db_connections, connection_pools

#### 🔧 전환 전략

##### 8.1 연결 설정 관리

```typescript
// 기존 Prisma 코드
const connections = await prisma.external_db_connections.findMany({
  where: { is_active: true },
  include: { connection_pools: true },
});

// 새로운 Raw Query 코드
const connections = await DatabaseManager.query(`
  SELECT 
    edc.*,
    json_agg(cp.*) as connection_pools
  FROM external_db_connections edc
  LEFT JOIN connection_pools cp ON edc.id = cp.connection_id
  WHERE edc.is_active = true
  GROUP BY edc.id
`);
```

##### 8.2 연결 풀 관리

```typescript
// 외부 DB 연결 풀 생성 및 관리
class ExternalConnectionManager {
  private static pools = new Map<string, Pool>();

  static async getConnection(connectionId: string): Promise<PoolClient> {
    if (!this.pools.has(connectionId)) {
      const config = await this.getConnectionConfig(connectionId);
      this.pools.set(connectionId, new Pool(config));
    }

    return this.pools.get(connectionId)!.connect();
  }

  private static async getConnectionConfig(connectionId: string) {
    const [config] = await DatabaseManager.query(
      `
      SELECT host, port, database, username, password, ssl_config
      FROM external_db_connections 
      WHERE id = $1 AND is_active = true
    `,
      [connectionId]
    );

    return {
      host: config.host,
      port: config.port,
      database: config.database,
      user: config.username,
      password: EncryptUtil.decrypt(config.password),
      ssl: config.ssl_config,
    };
  }
}
```

---

## 🟢 Phase 4: 확장 기능 전환 (129개 호출)

### 9. adminController.ts (28개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 중간 (컨트롤러 레이어, 다양한 관리 기능)
- **주요 기능**: 관리자 메뉴, 사용자 관리, 권한 관리, 시스템 설정
- **의존성**: user_info, menu_info, auth_groups, company_mng

#### 🔧 전환 전략

##### 9.1 메뉴 관리 API 전환

```typescript
// 기존 Prisma 코드
export async function getAdminMenus(req: AuthenticatedRequest, res: Response) {
  const menus = await prisma.menu_info.findMany({
    where: {
      is_active: "Y",
      company_code: userCompanyCode,
    },
    include: {
      parent: true,
      children: { where: { is_active: "Y" } },
    },
    orderBy: { sort_order: "asc" },
  });

  res.json({ success: true, data: menus });
}

// 새로운 Raw Query 코드
export async function getAdminMenus(req: AuthenticatedRequest, res: Response) {
  // 계층형 메뉴 구조를 한 번의 쿼리로 조회
  const menus = await DatabaseManager.query(
    `
    WITH RECURSIVE menu_tree AS (
      SELECT 
        m.*,
        0 as level,
        ARRAY[m.sort_order] as path
      FROM menu_info m
      WHERE m.parent_id IS NULL 
        AND m.is_active = 'Y'
        AND m.company_code = $1
      
      UNION ALL
      
      SELECT 
        m.*,
        mt.level + 1,
        mt.path || m.sort_order
      FROM menu_info m
      JOIN menu_tree mt ON m.parent_id = mt.id
      WHERE m.is_active = 'Y'
    )
    SELECT * FROM menu_tree 
    ORDER BY path
  `,
    [userCompanyCode]
  );

  res.json({ success: true, data: menus });
}
```

##### 9.2 사용자 관리 API 전환

```typescript
// 기존 Prisma 코드
export async function getUserList(req: AuthenticatedRequest, res: Response) {
  const users = await prisma.user_info.findMany({
    where: { company_code: userCompanyCode },
    include: {
      dept_info: true,
      user_auth: { include: { auth_group: true } },
    },
  });
}

// 새로운 Raw Query 코드
export async function getUserList(req: AuthenticatedRequest, res: Response) {
  const users = await DatabaseManager.query(
    `
    SELECT 
      ui.*,
      di.dept_name,
      json_agg(
        json_build_object(
          'auth_code', ag.auth_code,
          'auth_name', ag.auth_name
        )
      ) FILTER (WHERE ag.auth_code IS NOT NULL) as authorities
    FROM user_info ui
    LEFT JOIN dept_info di ON ui.dept_code = di.dept_code
    LEFT JOIN user_auth ua ON ui.user_id = ua.user_id
    LEFT JOIN auth_group ag ON ua.auth_code = ag.auth_code
    WHERE ui.company_code = $1
    GROUP BY ui.user_id, di.dept_name
    ORDER BY ui.created_date DESC
  `,
    [userCompanyCode]
  );
}
```

##### 9.3 권한 관리 API 전환

```typescript
// 복잡한 권한 체크 로직
export async function checkUserPermission(
  req: AuthenticatedRequest,
  res: Response
) {
  const { menuUrl } = req.body;

  const hasPermission = await DatabaseManager.query(
    `
    SELECT EXISTS (
      SELECT 1 
      FROM user_auth ua
      JOIN auth_group ag ON ua.auth_code = ag.auth_code
      JOIN menu_auth_group mag ON ag.auth_code = mag.auth_code
      JOIN menu_info mi ON mag.menu_id = mi.id
      WHERE ua.user_id = $1 
        AND mi.url = $2
        AND ua.is_active = 'Y'
        AND ag.is_active = 'Y'
        AND mi.is_active = 'Y'
    ) as has_permission
  `,
    [req.user.userId, menuUrl]
  );

  res.json({ success: true, hasPermission: hasPermission[0].has_permission });
}
```

#### 🧪 테스트 전략

1. **API 응답 테스트**: 기존 API와 동일한 응답 구조 확인
2. **권한 테스트**: 다양한 사용자 권한 시나리오
3. **계층 구조 테스트**: 메뉴 트리 구조 정확성
4. **성능 테스트**: 복잡한 조인 쿼리 성능

---

### 10. componentStandardService.ts (16개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 중간 (컴포넌트 표준 관리)
- **주요 기능**: UI 컴포넌트 표준 정의, 템플릿 관리
- **의존성**: component_standards, ui_templates

#### 🔧 전환 전략

##### 10.1 컴포넌트 표준 조회

```typescript
// 기존 Prisma 코드
const components = await prisma.component_standards.findMany({
  where: { category: category },
  include: {
    templates: { where: { is_active: true } },
    properties: true,
  },
});

// 새로운 Raw Query 코드
const components = await DatabaseManager.query(
  `
  SELECT 
    cs.*,
    json_agg(
      DISTINCT jsonb_build_object(
        'id', ut.id,
        'template_name', ut.template_name,
        'template_config', ut.template_config
      )
    ) FILTER (WHERE ut.id IS NOT NULL) as templates,
    json_agg(
      DISTINCT jsonb_build_object(
        'property_name', cp.property_name,
        'property_type', cp.property_type,
        'default_value', cp.default_value
      )
    ) FILTER (WHERE cp.id IS NOT NULL) as properties
  FROM component_standards cs
  LEFT JOIN ui_templates ut ON cs.id = ut.component_id AND ut.is_active = true
  LEFT JOIN component_properties cp ON cs.id = cp.component_id
  WHERE cs.category = $1
  GROUP BY cs.id
`,
  [category]
);
```

---

### 11. commonCodeService.ts (15개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 중간 (코드 관리, 계층 구조)
- **주요 기능**: 공통 코드 관리, 코드 카테고리 관리
- **의존성**: code_info, code_category

#### 🔧 전환 전략

##### 11.1 계층형 코드 구조 처리

```typescript
// 기존 Prisma 코드
const codes = await prisma.code_info.findMany({
  where: { category_code: categoryCode },
  include: {
    parent: true,
    children: { where: { is_active: "Y" } },
  },
});

// 새로운 Raw Query 코드 (재귀 CTE 사용)
const codes = await DatabaseManager.query(
  `
  WITH RECURSIVE code_tree AS (
    SELECT 
      ci.*,
      0 as level,
      CAST(ci.sort_order as TEXT) as path
    FROM code_info ci
    WHERE ci.parent_code IS NULL 
      AND ci.category_code = $1
      AND ci.is_active = 'Y'
    
    UNION ALL
    
    SELECT 
      ci.*,
      ct.level + 1,
      ct.path || '.' || ci.sort_order
    FROM code_info ci
    JOIN code_tree ct ON ci.parent_code = ct.code
    WHERE ci.is_active = 'Y'
  )
  SELECT * FROM code_tree 
  ORDER BY path
`,
  [categoryCode]
);
```

---

### 12. batchService.ts (16개 호출)

#### 📊 현재 상태 분석

- **복잡도**: 중간-높음 (배치 작업, 스케줄링)
- **주요 기능**: 배치 작업 관리, 실행 이력, 스케줄링
- **의존성**: batch_configs, batch_execution_logs

#### 🔧 전환 전략

##### 12.1 배치 실행 이력 관리

```typescript
// 기존 Prisma 코드
const batchHistory = await prisma.batch_execution_logs.findMany({
  where: { batch_config_id: configId },
  include: { batch_config: true },
  orderBy: { created_at: "desc" },
  take: 50,
});

// 새로운 Raw Query 코드
const batchHistory = await DatabaseManager.query(
  `
  SELECT 
    bel.*,
    bc.batch_name,
    bc.description as batch_description
  FROM batch_execution_logs bel
  JOIN batch_configs bc ON bel.batch_config_id = bc.id
  WHERE bel.batch_config_id = $1
  ORDER BY bel.created_at DESC
  LIMIT 50
`,
  [configId]
);
```

##### 12.2 배치 상태 업데이트

```typescript
// 트랜잭션을 사용한 배치 상태 관리
async updateBatchStatus(batchId: number, status: string, result?: any) {
  await DatabaseManager.transaction(async (client) => {
    // 실행 로그 업데이트
    await client.query(`
      UPDATE batch_execution_logs
      SET status = $1,
          result = $2,
          completed_at = NOW(),
          updated_at = NOW()
      WHERE id = $3
    `, [status, result, batchId]);

    // 배치 설정의 마지막 실행 시간 업데이트
    await client.query(`
      UPDATE batch_configs
      SET last_executed_at = NOW(),
          last_status = $1,
          updated_at = NOW()
      WHERE id = (
        SELECT batch_config_id
        FROM batch_execution_logs
        WHERE id = $2
      )
    `, [status, batchId]);
  });
}
```

---

## 📋 나머지 파일들 요약 전환 계획

### Phase 2 나머지 파일들 (6개 호출)

- **dataflowControlService.ts** (6개): 제어 로직, 조건부 실행
- **ddlExecutionService.ts** (6개): DDL 실행, 스키마 변경
- **authService.ts** (5개): 사용자 인증, 토큰 관리
- **multiConnectionQueryService.ts** (4개): 다중 DB 연결

### Phase 3 나머지 파일들 (121개 호출)

- **dataflowDiagramService.ts** (12개): 다이어그램 관리, JSON 처리
- **collectionService.ts** (11개): 컬렉션 관리
- **layoutService.ts** (10개): 레이아웃 관리
- **dbTypeCategoryService.ts** (10개): DB 타입 분류
- **templateStandardService.ts** (9개): 템플릿 표준
- **ddlAuditLogger.ts** (8개): DDL 감사 로그
- **externalCallConfigService.ts** (8개): 외부 호출 설정
- **batchExternalDbService.ts** (8개): 배치 외부DB
- **batchExecutionLogService.ts** (7개): 배치 실행 로그
- **eventTriggerService.ts** (6개): 이벤트 트리거
- **enhancedDynamicFormService.ts** (6개): 확장 동적 폼
- **entityJoinService.ts** (5개): 엔티티 조인
- **dataMappingService.ts** (5개): 데이터 매핑
- **batchManagementService.ts** (5개): 배치 관리
- **batchSchedulerService.ts** (4개): 배치 스케줄러
- **dataService.ts** (4개): 데이터 서비스
- **adminService.ts** (3개): 관리자 서비스
- **referenceCacheService.ts** (3개): 참조 캐시

### Phase 4 나머지 파일들 (101개 호출)

- **webTypeStandardController.ts** (11개): 웹타입 표준 컨트롤러
- **fileController.ts** (11개): 파일 업로드/다운로드 컨트롤러
- **buttonActionStandardController.ts** (11개): 버튼 액션 표준
- **entityReferenceController.ts** (4개): 엔티티 참조 컨트롤러
- **database.ts** (4개): 데이터베이스 설정
- **dataflowExecutionController.ts** (3개): 데이터플로우 실행
- **screenFileController.ts** (2개): 화면 파일 컨트롤러
- **ddlRoutes.ts** (2개): DDL 라우트
- **companyManagementRoutes.ts** (2개): 회사 관리 라우트

---

## 🔗 파일 간 의존성 분석

### 1. 핵심 의존성 체인

```
DatabaseManager (기반)
  ↓
QueryBuilder (쿼리 생성)
  ↓
Services (비즈니스 로직)
  ↓
Controllers (API 엔드포인트)
  ↓
Routes (라우팅)
```

### 2. 서비스 간 의존성

```
tableManagementService
  ↓ (테이블 메타데이터)
screenManagementService
  ↓ (화면 정의)
dynamicFormService
  ↓ (폼 데이터)
dataflowControlService
```

### 3. 전환 순서 (의존성 고려)

1. **기반 구조**: DatabaseManager, QueryBuilder
2. **메타데이터 서비스**: tableManagementService
3. **핵심 비즈니스**: screenManagementService, dataflowService
4. **폼 처리**: dynamicFormService
5. **외부 연동**: externalDbConnectionService
6. **관리 기능**: adminService, commonCodeService
7. **배치 시스템**: batchService 계열
8. **컨트롤러**: adminController 등
9. **라우트**: 각종 라우트 파일들

---

## ⚡ 전환 가속화 전략

### 1. 병렬 전환 가능 그룹

```
그룹 A (독립적): authService, adminService, commonCodeService
그룹 B (배치 관련): batchService, batchSchedulerService, batchExecutionLogService
그룹 C (컨트롤러): adminController, fileController, webTypeStandardController
그룹 D (표준 관리): componentStandardService, templateStandardService
```

### 2. 공통 패턴 템플릿 활용

```typescript
// 표준 CRUD 템플릿
class StandardCRUDTemplate {
  static async create(tableName: string, data: any) {
    const { query, params } = QueryBuilder.insert(tableName, data, {
      returning: ["*"],
    });
    return await DatabaseManager.query(query, params);
  }

  static async findMany(tableName: string, options: any) {
    const { query, params } = QueryBuilder.select(tableName, options);
    return await DatabaseManager.query(query, params);
  }

  // ... 기타 표준 메서드들
}
```

### 3. 자동화 도구 활용

```typescript
// Prisma → Raw Query 자동 변환 도구
class PrismaToRawConverter {
  static convertFindMany(prismaCall: string): string {
    // Prisma findMany 호출을 Raw Query로 자동 변환
  }

  static convertCreate(prismaCall: string): string {
    // Prisma create 호출을 Raw Query로 자동 변환
  }
}
```

---

## 🧪 통합 테스트 전략

### 1. 파일별 테스트 매트릭스

| 파일명                  | 단위테스트 | 통합테스트 | 성능테스트 | E2E테스트 |
| ----------------------- | ---------- | ---------- | ---------- | --------- |
| screenManagementService | ✅         | ✅         | ✅         | ✅        |
| tableManagementService  | ✅         | ✅         | ✅         | ❌        |
| dataflowService         | ✅         | ✅         | ❌         | ❌        |
| adminController         | ✅         | ✅         | ❌         | ✅        |

### 2. 회귀 테스트 자동화

```typescript
// 기존 Prisma vs 새로운 Raw Query 결과 비교
describe("Migration Regression Tests", () => {
  test("screenManagementService.getScreens should return identical results", async () => {
    const prismaResult = await oldScreenService.getScreens(params);
    const rawQueryResult = await newScreenService.getScreens(params);

    expect(normalizeResult(rawQueryResult)).toEqual(
      normalizeResult(prismaResult)
    );
  });
});
```

### 3. 성능 벤치마크

```typescript
// 성능 비교 테스트
describe("Performance Benchmarks", () => {
  test("Complex query performance comparison", async () => {
    const iterations = 1000;

    const prismaTime = await measureTime(
      () => prismaService.complexQuery(params),
      iterations
    );

    const rawQueryTime = await measureTime(
      () => rawQueryService.complexQuery(params),
      iterations
    );

    expect(rawQueryTime).toBeLessThanOrEqual(prismaTime * 1.1); // 10% 허용
  });
});
```

---

## 🔧 공통 전환 패턴

### 1. 기본 CRUD 패턴

```typescript
// CREATE
const { query, params } = QueryBuilder.insert(tableName, data, {
  returning: ["*"],
});
const [result] = await DatabaseManager.query(query, params);

// READ
const { query, params } = QueryBuilder.select(tableName, {
  where,
  orderBy,
  limit,
});
const results = await DatabaseManager.query(query, params);

// UPDATE
const { query, params } = QueryBuilder.update(tableName, data, where);
const [result] = await DatabaseManager.query(query, params);

// DELETE
const { query, params } = QueryBuilder.delete(tableName, where);
const results = await DatabaseManager.query(query, params);
```

### 2. 트랜잭션 패턴

```typescript
await DatabaseManager.transaction(async (client) => {
  const result1 = await client.query(query1, params1);
  const result2 = await client.query(query2, params2);
  return { result1, result2 };
});
```

### 3. 동적 쿼리 패턴

```typescript
const tableName = DatabaseValidator.sanitizeTableName(userInput);
const columnName = DatabaseValidator.sanitizeColumnName(userInput);
const query = `SELECT ${columnName} FROM ${tableName} WHERE id = $1`;
const result = await DatabaseManager.query(query, [id]);
```

---

## 📋 전환 체크리스트

### 각 파일별 필수 확인사항

- [ ] 모든 Prisma 호출 식별 및 변환
- [ ] 타입 변환 (Date, JSON, BigInt) 처리
- [ ] NULL 값 처리 일관성
- [ ] 트랜잭션 경계 유지
- [ ] 에러 처리 로직 보존
- [ ] 성능 최적화 (인덱스 힌트 등)
- [ ] 단위 테스트 작성
- [ ] 통합 테스트 실행
- [ ] 기능 동작 검증
- [ ] 성능 비교 테스트

### 공통 주의사항

1. **SQL 인젝션 방지**: 모든 동적 쿼리에 파라미터 바인딩 사용
2. **타입 안전성**: TypeScript 타입 정의 유지
3. **에러 처리**: Prisma 에러를 적절한 HTTP 상태코드로 변환
4. **로깅**: 쿼리 실행 로그 및 성능 모니터링
5. **백워드 호환성**: API 응답 형식 유지

---

## 🎯 성공 기준

### 기능적 요구사항

- [ ] 모든 기존 API 엔드포인트 정상 동작
- [ ] 데이터 일관성 유지
- [ ] 트랜잭션 무결성 보장
- [ ] 에러 처리 동일성

### 성능 요구사항

- [ ] 응답 시간 기존 대비 ±10% 이내
- [ ] 메모리 사용량 최적화
- [ ] 동시 접속 처리 능력 유지

### 품질 요구사항

- [ ] 코드 커버리지 90% 이상
- [ ] 타입 안전성 보장
- [ ] 보안 검증 통과
- [ ] 문서화 완료

이 상세 계획을 통해 각 파일별로 체계적이고 안전한 마이그레이션을 수행할 수 있습니다.
