package com.erp.service; import com.erp.common.BaseService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; @Service @RequiredArgsConstructor @Slf4j public class CodeMergeService extends BaseService { private final JdbcTemplate jdbcTemplate; private static final String NS = "codeMerge."; // ── Tables With Column ──────────────────────────────────────────────────── /** * GET /tables-with-column/:columnName * 해당 컬럼과 company_code 컬럼을 함께 가진 public 테이블 목록 반환 */ public Map getTablesWithColumn(String columnName) { Map params = new HashMap<>(); params.put("column_name", columnName); List> rows = sqlSession.selectList(NS + "getTablesWithColumn", params); List tables = rows.stream() .map(r -> { Object val = r.get("table_name"); return val != null ? val.toString() : null; }) .filter(Objects::nonNull) .collect(Collectors.toList()); Map result = new LinkedHashMap<>(); result.put("column_name", columnName); result.put("tables", tables); result.put("count", tables.size()); return result; } // ── Preview (column-based) ──────────────────────────────────────────────── /** * POST /preview * columnName + oldValue 기준으로 영향받을 테이블/행 수 미리보기 (DB 변경 없음) */ public Map previewCodeMerge(Map body) { String columnName = str(body.get("column_name")); String oldValue = str(body.get("old_value")); String companyCode = str(body.get("company_code")); if (isBlank(columnName) || isBlank(oldValue)) { throw new IllegalArgumentException("필수 필드가 누락되었습니다. (columnName, oldValue)"); } log.info("코드 병합 미리보기: column={}, oldValue={}, company={}", columnName, oldValue, companyCode); Map params = new HashMap<>(); params.put("column_name", columnName); List> tableRows = sqlSession.selectList(NS + "getTablesWithColumn", params); List> preview = new ArrayList<>(); int totalRows = 0; for (Map tableRow : tableRows) { Object nameVal = tableRow.get("table_name"); if (nameVal == null) continue; String tableName = nameVal.toString(); // 테이블명·컬럼명은 information_schema에서 검증된 값 — SQL 인젝션 위험 없음 String countSql = String.format( "SELECT COUNT(*) FROM \"%s\" WHERE \"%s\" = ? AND company_code = ?", tableName, columnName); try { Integer count = jdbcTemplate.queryForObject(countSql, Integer.class, oldValue, companyCode); if (count != null && count > 0) { Map item = new LinkedHashMap<>(); item.put("table_name", tableName); item.put("affected_rows", count); preview.add(item); totalRows += count; } } catch (Exception e) { log.warn("테이블 {} 조회 실패 (건너뜀): {}", tableName, e.getMessage()); } } Map result = new LinkedHashMap<>(); result.put("column_name", columnName); result.put("old_value", oldValue); result.put("preview", preview); result.put("total_affected_rows", totalRows); return result; } // ── Merge All Tables (column-based) ─────────────────────────────────────── /** * POST /merge-all-tables * PostgreSQL 함수 merge_code_all_tables(columnName, oldValue, newValue, companyCode) 호출 */ @Transactional public Map mergeAllTables(Map body) { String columnName = str(body.get("column_name")); String oldValue = str(body.get("old_value")); String newValue = str(body.get("new_value")); String companyCode = str(body.get("company_code")); if (isBlank(columnName) || isBlank(oldValue) || isBlank(newValue)) { throw new IllegalArgumentException("필수 필드가 누락되었습니다. (columnName, oldValue, newValue)"); } if (oldValue.equals(newValue)) { throw new IllegalArgumentException("기존 값과 새 값이 동일합니다."); } log.info("코드 병합 시작: column={}, {} → {}, company={}", columnName, oldValue, newValue, companyCode); List> rows = jdbcTemplate.queryForList( "SELECT * FROM merge_code_all_tables(?, ?, ?, ?)", columnName, oldValue, newValue, companyCode); int totalRows = rows.stream() .mapToInt(r -> r.get("rows_updated") != null ? ((Number) r.get("rows_updated")).intValue() : 0) .sum(); List> affectedTables = rows.stream().map(r -> { Map item = new LinkedHashMap<>(); item.put("table_name", r.get("table_name")); item.put("rows_updated", r.get("rows_updated") != null ? ((Number) r.get("rows_updated")).intValue() : 0); return item; }).collect(Collectors.toList()); log.info("코드 병합 완료: 영향 테이블 {}개, 총 {}행", affectedTables.size(), totalRows); Map result = new LinkedHashMap<>(); result.put("column_name", columnName); result.put("old_value", oldValue); result.put("new_value", newValue); result.put("affected_tables", affectedTables); result.put("total_rows_updated", totalRows); return result; } // ── Merge By Value ──────────────────────────────────────────────────────── /** * POST /merge-by-value * PostgreSQL 함수 merge_code_by_value(oldValue, newValue, companyCode) 호출 * 컬럼명에 관계없이 해당 값을 가진 모든 위치를 변경 */ @Transactional public Map mergeByValue(Map body) { String oldValue = str(body.get("old_value")); String newValue = str(body.get("new_value")); String companyCode = str(body.get("company_code")); if (isBlank(oldValue) || isBlank(newValue)) { throw new IllegalArgumentException("필수 필드가 누락되었습니다. (oldValue, newValue)"); } if (oldValue.equals(newValue)) { throw new IllegalArgumentException("기존 값과 새 값이 동일합니다."); } log.info("값 기반 코드 병합 시작: {} → {}, company={}", oldValue, newValue, companyCode); List> rows = jdbcTemplate.queryForList( "SELECT * FROM merge_code_by_value(?, ?, ?)", oldValue, newValue, companyCode); int totalRows = rows.stream() .mapToInt(r -> r.get("out_rows_updated") != null ? ((Number) r.get("out_rows_updated")).intValue() : 0) .sum(); List> affectedData = rows.stream().map(r -> { Map item = new LinkedHashMap<>(); item.put("table_name", r.get("out_table_name")); item.put("column_name", r.get("out_column_name")); item.put("rows_updated", r.get("out_rows_updated") != null ? ((Number) r.get("out_rows_updated")).intValue() : 0); return item; }).collect(Collectors.toList()); log.info("값 기반 코드 병합 완료: {} → {}, 총 {}행", oldValue, newValue, totalRows); Map result = new LinkedHashMap<>(); result.put("old_value", oldValue); result.put("new_value", newValue); result.put("affected_data", affectedData); result.put("total_rows_updated", totalRows); return result; } // ── Preview By Value ────────────────────────────────────────────────────── /** * POST /preview-by-value * PostgreSQL 함수 preview_merge_code_by_value(oldValue, companyCode) 호출 */ public Map previewByValue(Map body) { String oldValue = str(body.get("old_value")); String companyCode = str(body.get("company_code")); if (isBlank(oldValue)) { throw new IllegalArgumentException("필수 필드가 누락되었습니다. (oldValue)"); } log.info("값 기반 코드 병합 미리보기: oldValue={}, company={}", oldValue, companyCode); List> rows = jdbcTemplate.queryForList( "SELECT * FROM preview_merge_code_by_value(?, ?)", oldValue, companyCode); int totalRows = rows.stream() .mapToInt(r -> r.get("out_affected_rows") != null ? ((Number) r.get("out_affected_rows")).intValue() : 0) .sum(); List> preview = rows.stream().map(r -> { Map item = new LinkedHashMap<>(); item.put("table_name", r.get("out_table_name")); item.put("column_name", r.get("out_column_name")); item.put("affected_rows", r.get("out_affected_rows") != null ? ((Number) r.get("out_affected_rows")).intValue() : 0); return item; }).collect(Collectors.toList()); Map result = new LinkedHashMap<>(); result.put("old_value", oldValue); result.put("preview", preview); result.put("total_affected_rows", totalRows); return result; } // ── Helpers ─────────────────────────────────────────────────────────────── private String str(Object val) { return val != null ? val.toString() : null; } private boolean isBlank(String s) { return s == null || s.isBlank(); } }