|
|
|
|
@ -13,6 +13,7 @@ import java.io.OutputStream;
|
|
|
|
|
import java.lang.reflect.Field;
|
|
|
|
|
import java.security.GeneralSecurityException;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
|
|
|
|
|
import static egovframework.util.excel.SuperClassReflectionUtils.getAllFieldsWithExcelColumn;
|
|
|
|
|
|
|
|
|
|
@ -208,6 +209,17 @@ public abstract class BaseSxssfExcelFile implements ExcelFile {
|
|
|
|
|
protected void renderDataLines(ExcelSheetData data, ExcelMetadata metadata) {
|
|
|
|
|
CellStyle dataStyle = createCellStyle(workbook, false);
|
|
|
|
|
|
|
|
|
|
// 숫자 서식 스타일 생성(정수/소수) - 기본 스타일을 복제하여 데이터 포맷만 부여
|
|
|
|
|
CellStyle integerNumberStyle = workbook.createCellStyle();
|
|
|
|
|
integerNumberStyle.cloneStyleFrom(dataStyle);
|
|
|
|
|
short intDf = workbook.createDataFormat().getFormat("#,##0");
|
|
|
|
|
integerNumberStyle.setDataFormat(intDf);
|
|
|
|
|
|
|
|
|
|
CellStyle decimalNumberStyle = workbook.createCellStyle();
|
|
|
|
|
decimalNumberStyle.cloneStyleFrom(dataStyle);
|
|
|
|
|
short decDf = workbook.createDataFormat().getFormat("#,##0.##");
|
|
|
|
|
decimalNumberStyle.setDataFormat(decDf);
|
|
|
|
|
|
|
|
|
|
// 데이터 시작 행 (제목이 있으면 row 2, 없으면 row 1)
|
|
|
|
|
int rowIndex = ROW_START_INDEX + titleRowOffset + 1;
|
|
|
|
|
List<Field> fields = getAllFieldsWithExcelColumn(data.getType());
|
|
|
|
|
@ -215,7 +227,7 @@ public abstract class BaseSxssfExcelFile implements ExcelFile {
|
|
|
|
|
// 각 데이터 객체를 행으로 변환
|
|
|
|
|
for (Object record : data.getDataList()) {
|
|
|
|
|
Row row = sheet.createRow(rowIndex++);
|
|
|
|
|
renderDataRow(row, record, fields, dataStyle);
|
|
|
|
|
renderDataRow(row, record, fields, dataStyle, integerNumberStyle, decimalNumberStyle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 컬럼 너비 조정
|
|
|
|
|
@ -239,6 +251,20 @@ public abstract class BaseSxssfExcelFile implements ExcelFile {
|
|
|
|
|
titleFont.setBold(true);
|
|
|
|
|
titleStyle.setFont(titleFont);
|
|
|
|
|
|
|
|
|
|
// 중요 로직(한글): 제목 셀에도 실선(THIN) 보더를 적용하여 표의 일관성 유지
|
|
|
|
|
titleStyle.setBorderTop(BorderStyle.THIN);
|
|
|
|
|
titleStyle.setBorderBottom(BorderStyle.THIN);
|
|
|
|
|
titleStyle.setBorderLeft(BorderStyle.THIN);
|
|
|
|
|
titleStyle.setBorderRight(BorderStyle.THIN);
|
|
|
|
|
|
|
|
|
|
// 중요 로직(한글): 제목 배경색 지정 - 요구사항에 따라 #be8e00 색상을 적용
|
|
|
|
|
// SXSSF에서는 내부적으로 XSSFCellStyle을 사용하므로 캐스팅하여 사용자 색상 설정
|
|
|
|
|
if (titleStyle instanceof org.apache.poi.xssf.usermodel.XSSFCellStyle) {
|
|
|
|
|
org.apache.poi.xssf.usermodel.XSSFCellStyle xssf = (org.apache.poi.xssf.usermodel.XSSFCellStyle) titleStyle;
|
|
|
|
|
xssf.setFillForegroundColor(new org.apache.poi.xssf.usermodel.XSSFColor(new java.awt.Color(0xBE, 0x8E, 0x00), null));
|
|
|
|
|
xssf.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return titleStyle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -250,6 +276,14 @@ public abstract class BaseSxssfExcelFile implements ExcelFile {
|
|
|
|
|
private CellStyle createHeaderCellStyle() {
|
|
|
|
|
CellStyle headerStyle = createCellStyle(workbook, true);
|
|
|
|
|
headerStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
|
|
|
|
|
// 중요 로직(한글): 헤더 배경색 지정 - 요구사항에 따라 #fde598 색상을 적용
|
|
|
|
|
if (headerStyle instanceof org.apache.poi.xssf.usermodel.XSSFCellStyle) {
|
|
|
|
|
org.apache.poi.xssf.usermodel.XSSFCellStyle xssf = (org.apache.poi.xssf.usermodel.XSSFCellStyle) headerStyle;
|
|
|
|
|
xssf.setFillForegroundColor(new org.apache.poi.xssf.usermodel.XSSFColor(new java.awt.Color(0xFD, 0xE5, 0x98), null));
|
|
|
|
|
xssf.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return headerStyle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -271,21 +305,90 @@ public abstract class BaseSxssfExcelFile implements ExcelFile {
|
|
|
|
|
* @param row 렌더링할 행
|
|
|
|
|
* @param record 데이터 객체
|
|
|
|
|
* @param fields ExcelColumn 어노테이션이 있는 필드 리스트
|
|
|
|
|
* @param style 셀 스타일
|
|
|
|
|
* @param baseStyle 기본 데이터 셀 스타일(보더 등 공통)
|
|
|
|
|
* @param integerNumberStyle 정수형 숫자 서식 스타일(#,##0)
|
|
|
|
|
* @param decimalNumberStyle 소수형 숫자 서식 스타일(#,##0.##)
|
|
|
|
|
* @throws RuntimeException 필드 접근 실패 시
|
|
|
|
|
*/
|
|
|
|
|
private void renderDataRow(Row row, Object record, List<Field> fields, CellStyle style) {
|
|
|
|
|
private void renderDataRow(Row row, Object record, List<Field> fields, CellStyle baseStyle, CellStyle integerNumberStyle, CellStyle decimalNumberStyle) {
|
|
|
|
|
int columnIndex = COLUMN_START_INDEX;
|
|
|
|
|
try {
|
|
|
|
|
for (Field field : fields) {
|
|
|
|
|
field.setAccessible(true);
|
|
|
|
|
createCell(row, columnIndex++, field.get(record), style);
|
|
|
|
|
|
|
|
|
|
Object value = field.get(record);
|
|
|
|
|
|
|
|
|
|
// 수식 처리: ExcelColumn에 formula 설정이 있는 경우 수식을 생성하여 설정
|
|
|
|
|
ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);
|
|
|
|
|
if (excelColumn != null && excelColumn.formula() && !excelColumn.formulaRefField().isEmpty()) {
|
|
|
|
|
String refFieldName = excelColumn.formulaRefField();
|
|
|
|
|
int refColumnIndex = findFieldColumnIndex(fields, refFieldName);
|
|
|
|
|
if (refColumnIndex >= 0) {
|
|
|
|
|
// 한글 중요 주석: 참조 대상(X열) 값이 null 또는 공백("")이면 수식을 적용하지 않고 빈 셀로 처리하여 컬럼 정렬 유지
|
|
|
|
|
Field refField = fields.get(refColumnIndex);
|
|
|
|
|
refField.setAccessible(true);
|
|
|
|
|
Object refValue = refField.get(record);
|
|
|
|
|
boolean isBlankRef = (refValue == null) || (refValue instanceof String && ((String) refValue).trim().isEmpty());
|
|
|
|
|
if (isBlankRef) {
|
|
|
|
|
Cell cell = row.createCell(columnIndex++);
|
|
|
|
|
cell.setCellValue("");
|
|
|
|
|
cell.setCellStyle(baseStyle);
|
|
|
|
|
continue; // 다음 필드 처리
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String refExcelColumn = toExcelColumnName(refColumnIndex + 1); // 1-based for Excel column letters
|
|
|
|
|
int excelRowNumber = row.getRowNum() + 1; // 1-based row number in Excel
|
|
|
|
|
String formula = String.format(excelColumn.formulaPattern(), refExcelColumn, excelRowNumber);
|
|
|
|
|
|
|
|
|
|
Cell cell = row.createCell(columnIndex++);
|
|
|
|
|
cell.setCellFormula(formula);
|
|
|
|
|
cell.setCellStyle(baseStyle);
|
|
|
|
|
continue; // 다음 필드 처리
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 기본 값 처리: 숫자 타입일 경우 천단위 콤마 서식 적용
|
|
|
|
|
CellStyle styleToUse = baseStyle;
|
|
|
|
|
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) {
|
|
|
|
|
styleToUse = integerNumberStyle;
|
|
|
|
|
} else if (value instanceof Float || value instanceof Double || value instanceof BigDecimal) {
|
|
|
|
|
styleToUse = decimalNumberStyle;
|
|
|
|
|
}
|
|
|
|
|
createCell(row, columnIndex++, value, styleToUse);
|
|
|
|
|
}
|
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
|
throw new RuntimeException("데이터 필드 접근 중 오류가 발생했습니다.", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 필드명으로 컬럼 인덱스를 찾습니다.
|
|
|
|
|
* 한글 중요 주석: ExcelColumn 필드 순서 기준으로 0부터 시작하는 인덱스를 반환.
|
|
|
|
|
*/
|
|
|
|
|
private int findFieldColumnIndex(List<Field> fields, String targetFieldName) {
|
|
|
|
|
for (int i = 0; i < fields.size(); i++) {
|
|
|
|
|
if (fields.get(i).getName().equals(targetFieldName)) {
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 1부터 시작하는 컬럼 번호를 엑셀 컬럼명(A, B, ... AA, AB ...)으로 변환합니다.
|
|
|
|
|
* 한글 중요 주석: 엑셀 수식에서 참조할 컬럼 문자열을 생성하기 위한 유틸.
|
|
|
|
|
*/
|
|
|
|
|
private String toExcelColumnName(int columnNumber) {
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
int num = columnNumber;
|
|
|
|
|
while (num > 0) {
|
|
|
|
|
int rem = (num - 1) % 26;
|
|
|
|
|
sb.insert(0, (char) ('A' + rem));
|
|
|
|
|
num = (num - 1) / 26;
|
|
|
|
|
}
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 각 컬럼의 너비를 조정합니다.
|
|
|
|
|
*
|
|
|
|
|
|