package kr.itn.itnhub.seed; import kr.itn.itnhub.provision.ChannelNaming; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.springframework.stereotype.Component; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; /** * 신유형 신청목록 시트를 대표기관 단위로 축약한다. * * 시트 배치 (1행 그룹제목, 2행 헤더, 3행부터 데이터): * B=연번1 C=연번2 F=기관명 G=부서명 H=담당자명 I=직급/직함 J=유선전화 K=이메일 * * 축약 규칙: * - 연번2가 "00"인 행이 대표기관이며 담당자 정보를 갖는다 * - 나머지 하위기관 행은 대표기관에 흡수한다 (008 경찰청의 서울청·부산청 등) * - 단 경찰청_치안정책연구소만 예외로 별도 기관으로 남긴다 */ @Component public class SeedParser { public static final String SHEET_NAME = "신유형 신청목록"; private static final int DATA_START_ROW_INDEX = 2; // 3행 private static final int COL_ORG_NO = 1; // B private static final int COL_SUB_NO = 2; // C private static final int COL_ORG_NAME = 5; // F private static final int COL_DEPT = 6; // G private static final int COL_MANAGER = 7; // H private static final int COL_TITLE = 8; // I private static final int COL_PHONE = 9; // J private static final int COL_EMAIL = 10; // K public List parse(InputStream xlsx) { try (Workbook wb = WorkbookFactory.create(xlsx)) { Sheet sheet = wb.getSheet(SHEET_NAME); if (sheet == null) { throw new SeedParseException( "통합문서에 '" + SHEET_NAME + "' 시트가 없습니다."); } return readRows(sheet); } catch (IOException e) { throw new SeedParseException("엑셀 파일을 읽지 못했습니다.", e); } } private List readRows(Sheet sheet) { List result = new ArrayList<>(); for (int i = DATA_START_ROW_INDEX; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (row == null) { continue; } String orgNo = orgNo(row); String orgName = string(row, COL_ORG_NAME); if (orgNo.isBlank() || orgName.isBlank()) { continue; } boolean representative = "00".equals(subNo(row)); boolean special = ChannelNaming.SPECIAL_ORG_NAME.equalsIgnoreCase(orgName); if (!representative && !special) { continue; // 하위기관은 대표기관에 흡수된다 } result.add(new SeedRow( orgNo, orgName, special ? orgNo + "-policy" : orgNo, nullIfBlank(string(row, COL_DEPT)), nullIfBlank(string(row, COL_MANAGER)), nullIfBlank(string(row, COL_TITLE)), nullIfBlank(string(row, COL_PHONE)), nullIfBlank(string(row, COL_EMAIL)))); } return result; } /** 연번은 항상 3자리 문자열이다. 숫자로 저장된 셀도 앞자리 0을 복원해 준다. */ private String orgNo(Row row) { String raw = string(row, COL_ORG_NO); return raw.isBlank() ? "" : pad3(raw); } private String subNo(Row row) { String raw = string(row, COL_SUB_NO); return raw.isBlank() ? "" : (raw.length() == 1 ? "0" + raw : raw); } private String pad3(String value) { String v = value.trim(); return v.length() >= 3 ? v : "0".repeat(3 - v.length()) + v; } private String string(Row row, int columnIndex) { Cell cell = row.getCell(columnIndex); if (cell == null) { return ""; } return switch (cell.getCellType()) { case STRING -> cell.getStringCellValue().trim(); case NUMERIC -> String.valueOf((long) cell.getNumericCellValue()); case BOOLEAN -> String.valueOf(cell.getBooleanCellValue()); default -> ""; }; } private String nullIfBlank(String value) { return (value == null || value.isBlank()) ? null : value; } }