diff --git a/pom.xml b/pom.xml index 91ffd4b2..6e0e6a81 100644 --- a/pom.xml +++ b/pom.xml @@ -160,6 +160,32 @@ 1.0.9 + + + org.jsoup + jsoup + 1.18.1 + + + com.itextpdf + html2pdf + 4.0.3 + + + com.itextpdf + kernel + 7.2.5 + + + com.itextpdf + layout + 7.2.5 + + + com.itextpdf + io + 7.2.5 + diff --git a/src/main/java/com/interplug/qcast/biz/displayItem/dto/ItemResponse.java b/src/main/java/com/interplug/qcast/biz/displayItem/dto/ItemResponse.java index 72814127..1a7dbc1b 100644 --- a/src/main/java/com/interplug/qcast/biz/displayItem/dto/ItemResponse.java +++ b/src/main/java/com/interplug/qcast/biz/displayItem/dto/ItemResponse.java @@ -9,6 +9,9 @@ public class ItemResponse { @Schema(description = "Itme Id") private String itemId; + @Schema(description = "Item No") + private String itemNo; + @Schema(description = "Item Name") private String itemName; diff --git a/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java b/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java index e9cdcc1d..270ac867 100644 --- a/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java +++ b/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java @@ -10,18 +10,21 @@ import com.interplug.qcast.biz.object.dto.ObjectResponse; import com.interplug.qcast.biz.object.dto.PlanRequest; import com.interplug.qcast.biz.object.dto.PlanResponse; import com.interplug.qcast.biz.pwrGnrSimulation.PwrGnrSimService; +import com.interplug.qcast.biz.pwrGnrSimulation.dto.PwrGnrSimGuideResponse; +import com.interplug.qcast.biz.pwrGnrSimulation.dto.PwrGnrSimRequest; +import com.interplug.qcast.biz.pwrGnrSimulation.dto.PwrGnrSimResponse; import com.interplug.qcast.config.Exception.ErrorCode; import com.interplug.qcast.config.Exception.QcastException; import com.interplug.qcast.config.message.Messages; import com.interplug.qcast.util.ExcelUtil; import com.interplug.qcast.util.InterfaceQsp; +import com.interplug.qcast.util.PdfUtil; import io.micrometer.common.util.StringUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; -import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.math.BigDecimal; @@ -30,6 +33,8 @@ import java.util.*; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpMethod; @@ -767,13 +772,7 @@ public class EstimateService { estimateResponse.setTotPrice( String.format("%1$,.0f", Double.parseDouble(estimateResponse.getTotPrice()))); - String excelFileName = - "Quation_Detail_" + new SimpleDateFormat("yyyyMMdd").format(new Date()); - String excelTemplatePath = - excelTemplateFilePath + File.separator + "excel_download_quotation_detail_template.xlsx"; - // 발전시뮬레이션 계산 - /* PwrGnrSimRequest pwrGnrSimRequest = new PwrGnrSimRequest(); pwrGnrSimRequest.setObjectNo(estimateResponse.getObjectNo()); pwrGnrSimRequest.setPlanNo(estimateResponse.getPlanNo()); @@ -781,7 +780,6 @@ public class EstimateService { PwrGnrSimResponse pwrGnrSimResponse = pwrGnrSimService.selectPwrGnrSimulation(pwrGnrSimRequest); if (pwrGnrSimResponse != null) { - try { // 발전시뮬레이션 안내사항 조회 PwrGnrSimGuideResponse pwrGnrSimGuideInfo = @@ -790,21 +788,51 @@ public class EstimateService { pwrGnrSimResponse.setGuideInfo(pwrGnrSimGuideInfo.getData()); } } catch (Exception e) { - } } estimateResponse.setPwrGnrSim(pwrGnrSimResponse); - */ + if (true) { + String[] arrSection = new String[3]; // TODO Section 갯수 넣기 + int iSection = 0; - excelUtil.download( - request, - response, - this.convertVoToMap(estimateResponse), - this.convertListToMap(estimateItemList), - excelFileName, - excelTemplatePath); + // PDF 다운로드 + String pdfFileName = + "Quation_Detail_" + new SimpleDateFormat("yyyyMMdd").format(new Date()); + String templateFilePath = "pdf_download_quotation_detail_template.html"; + + // 템플릿 html 조회 + Document doc = PdfUtil.getPdfDoc(request, templateFilePath); + Element elm; + + // 발전시뮬레이션 pdf Html 생성 + if (true) { + arrSection[iSection] = "div.section3"; + iSection++; + doc = pwrGnrSimService.pwrGnrSimPdfHtml(doc, pwrGnrSimResponse); + } else { + elm = doc.getElementsByClass("section3").first(); + elm.remove(); + } + + // pdf 다운로드 + PdfUtil.pdfDownload(request, response, doc, pdfFileName, arrSection); + + } else { + + String excelFileName = + "Quation_Detail_" + new SimpleDateFormat("yyyyMMdd").format(new Date()); + String excelTemplateNam = "excel_download_quotation_detail_template.xlsx"; + + excelUtil.download( + request, + response, + this.convertVoToMap(estimateResponse), + this.convertListToMap(estimateItemList), + excelFileName, + excelTemplateNam); + } } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/com/interplug/qcast/biz/pwrGnrSimulation/PwrGnrSimService.java b/src/main/java/com/interplug/qcast/biz/pwrGnrSimulation/PwrGnrSimService.java index 7a2d1be5..066faf51 100644 --- a/src/main/java/com/interplug/qcast/biz/pwrGnrSimulation/PwrGnrSimService.java +++ b/src/main/java/com/interplug/qcast/biz/pwrGnrSimulation/PwrGnrSimService.java @@ -7,19 +7,24 @@ import com.interplug.qcast.config.Exception.ErrorCode; import com.interplug.qcast.config.Exception.QcastException; import com.interplug.qcast.config.message.Messages; import com.interplug.qcast.util.InterfaceQsp; +import java.awt.*; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; @Slf4j @Service @@ -1303,4 +1308,181 @@ public class PwrGnrSimService { return guideResponse; } + + public static Document pwrGnrSimPdfHtml(Document doc, PwrGnrSimResponse data) + throws IOException, QcastException { + Element elm; + + // 상단 요약정보 + elm = doc.getElementById("objectNo"); + elm.text( + ObjectUtils.nullSafeToString(data.getObjectNo() + " (Plan No : " + data.getPlanNo() + ")")); + + elm = doc.getElementById("drawingEstimateCreateDate"); + elm.text(ObjectUtils.nullSafeToString(data.getDrawingEstimateCreateDate())); + + elm = doc.getElementById("prefName"); + elm.text(ObjectUtils.nullSafeToString(data.getPrefName())); + + elm = doc.getElementById("areaName"); + elm.text(ObjectUtils.nullSafeToString(data.getAreaName())); + + elm = doc.getElementById("capacity"); + elm.text(ObjectUtils.nullSafeToString(data.getCapacity())); + + elm = doc.getElementById("anlFrcsGnrt"); + elm.text(ObjectUtils.nullSafeToString(data.getAnlFrcsGnrt())); + + elm = doc.getElementById("snowfall"); + elm.text(ObjectUtils.nullSafeToString(data.getSnowfall())); + + elm = doc.getElementById("standardWindSpeedId"); + elm.text(ObjectUtils.nullSafeToString(data.getStandardWindSpeedId())); + + if (data.getFrcPwrGnrList() != null && data.getFrcPwrGnrList().length > 0) { + + int[] onlyData = + Arrays.copyOfRange(data.getFrcPwrGnrList(), 0, data.getFrcPwrGnrList().length - 1); + + int referenceValue = 300; // table 높이 + int orgMaxValue = + Arrays.stream(onlyData) + .max() + .orElseThrow(() -> new QcastException(ErrorCode.INTERNAL_SERVER_ERROR, "배열이 없음")); + int maxValue = roundUpAuto(orgMaxValue / 8); + + // chart y축 + StringBuilder sb = new StringBuilder(); + for (int i = 8; i >= 1; i--) { + sb.append(""); + sb.append("") + .append(maxValue * i) + .append(""); + sb.append(""); + } + sb.append(""); + sb.append("0"); + elm = doc.getElementById("htmlY"); + elm.append(sb.toString()); + + // chart 데이터 + String[] color = { + "#B5D4F5", "#FFE899", "#FBC3AB", "#D1D1D1", "#FFE899", "#B5D0F0", "#C1E4B8", "#A3C9EE", + "#F7BBA2", "#BEBEBE", "#A8BBE0", "#B5D7A3" + }; + StringBuilder sb2 = new StringBuilder(); + for (int i = 0; i < onlyData.length; i++) { + double scaledValues = ((double) onlyData[i] / (maxValue * 8)) * referenceValue; + + sb2.append(""); + + sb2.append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("
"); + + sb2.append( + "
"); + sb2.append(""); + } + System.err.println("htmlx :: " + sb2.toString()); + elm = doc.getElementById("htmlX"); + elm.append(sb2.toString()); + } + + // 예측발전량 + if (data.getFrcPwrGnrList() != null && data.getFrcPwrGnrList().length > 0) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 13; i++) { + sb.append("" + ObjectUtils.nullSafeToString(data.getFrcPwrGnrList()[i]) + ""); + } + + elm = doc.getElementById("frcPwrGnrList_detail"); + elm.append(sb.toString()); + } + + // 모듈 list + if (data.getRoofModuleList() != null && data.getRoofModuleList().size() > 0) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < data.getRoofModuleList().size(); i++) { + PwrGnrSimRoofResponse listItem = data.getRoofModuleList().get(i); + + sb.append(""); + sb.append("" + ObjectUtils.nullSafeToString(listItem.getRoofSurface()) + ""); + sb.append( + "" + + Optional.ofNullable(listItem.getSlope()).orElse("") + + "" + + Optional.ofNullable(listItem.getAngle()).orElse("") + + ""); + sb.append("" + ObjectUtils.nullSafeToString(listItem.getAzimuth()) + ""); + sb.append("" + ObjectUtils.nullSafeToString(listItem.getItemNo()) + ""); + sb.append("" + ObjectUtils.nullSafeToString(listItem.getAmount()) + ""); + sb.append(""); + } + + elm = doc.getElementById("roofModuleList_detail"); + elm.append(sb.toString()); + } + + // pcs list + if (data.getPcsList() != null && data.getPcsList().size() > 0) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < data.getPcsList().size(); i++) { + PwrGnrSimRoofResponse listItem = data.getPcsList().get(i); + + sb.append(""); + sb.append("" + ObjectUtils.nullSafeToString(listItem.getItemNo()) + ""); + sb.append("" + ObjectUtils.nullSafeToString(listItem.getAmount()) + ""); + sb.append(""); + } + + elm = doc.getElementById("pcsList_detail"); + elm.append(sb.toString()); + } + + return doc; + } + + public static int roundUpAuto(double value) { + int length = (int) Math.log10(value) + 1; // 숫자의 자리수를 계산 + int place = (int) Math.pow(10, length - 1); // 자리수에 따른 올림 기준(10, 100, 1000 등) + + return (int) Math.ceil(value / place) * place; + } } diff --git a/src/main/java/com/interplug/qcast/util/ExcelUtil.java b/src/main/java/com/interplug/qcast/util/ExcelUtil.java index 66d5b424..90213d47 100644 --- a/src/main/java/com/interplug/qcast/util/ExcelUtil.java +++ b/src/main/java/com/interplug/qcast/util/ExcelUtil.java @@ -26,7 +26,9 @@ public class ExcelUtil { * @param map 엑셀 출력데이터 * @param list 엑셀 출력 목록 데이터 * @param fileName 다운로드 파일명 - * @param templateFilePath 템플릿 파일경로 + * @param templateFileName + * @throws ParsePropertyException + * @throws InvalidFormatException */ public void download( HttpServletRequest request, @@ -34,10 +36,15 @@ public class ExcelUtil { Map map, List> list, String fileName, - String templateFilePath) + String templateFileName) throws ParsePropertyException, InvalidFormatException { try { - InputStream is = new BufferedInputStream(new FileInputStream(templateFilePath)); + + String templateFilePath = "template/excel/" + templateFileName; + InputStream templateStream = + PdfUtil.class.getClassLoader().getResourceAsStream(templateFilePath); + + InputStream is = new BufferedInputStream(templateStream); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + ".xlsx\""); diff --git a/src/main/java/com/interplug/qcast/util/PdfUtil.java b/src/main/java/com/interplug/qcast/util/PdfUtil.java new file mode 100644 index 00000000..cc6697f8 --- /dev/null +++ b/src/main/java/com/interplug/qcast/util/PdfUtil.java @@ -0,0 +1,151 @@ +package com.interplug.qcast.util; + +import com.itextpdf.html2pdf.ConverterProperties; +import com.itextpdf.html2pdf.HtmlConverter; +import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfReader; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.kernel.utils.PdfMerger; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class PdfUtil { + // HTML 템플릿 파일 경로 + // @Value("${file.excel.template.path}") + // private String excelTemplateFilePath; + + /** + * 파일 템플릿 HTML 불러오기 + * + * @param request + * @param templateFileName + * @return + * @throws IOException + */ + public static Document getPdfDoc(HttpServletRequest request, String templateFileName) + throws IOException { + + String templateFilePath = "template/pdf/" + templateFileName; + InputStream templateStream = + PdfUtil.class.getClassLoader().getResourceAsStream(templateFilePath); + + if (templateStream == null) { + throw new FileNotFoundException("Template file not found: " + templateFilePath); + } + + Document doc = Jsoup.parse(templateStream, StandardCharsets.UTF_8.name(), ""); + + return doc; + } + + /** + * PDF 다운로드 + * + * @param request + * @param response + * @param doc + * @param pdfFileName 파일명 + * @param arrSection 노출 Section + * @throws IOException + */ + public static void pdfDownload( + HttpServletRequest request, + HttpServletResponse response, + Document doc, + String pdfFileName, + String[] arrSection) + throws IOException { + + // 응답에 PDF 설정 + response.setContentType("application/pdf"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + pdfFileName + "\""); + + try (OutputStream os = response.getOutputStream(); + PdfWriter writer = new PdfWriter(os); + PdfDocument pdfDoc = new PdfDocument(writer)) { + + // ConverterProperties 생성 + ConverterProperties properties = new ConverterProperties(); + + // 폰트 설정 + DefaultFontProvider fontProvider = new DefaultFontProvider(false, false, false); + String fontPath = "template/pdf/BIZUDPGothic-Regular.ttf"; + InputStream fontStream = PdfUtil.class.getClassLoader().getResourceAsStream(fontPath); + + if (fontStream != null) { + File tempFontFile = Files.createTempFile("BIZUDPGothic-Regular", ".ttf").toFile(); + Files.copy( + fontStream, tempFontFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + fontProvider.addFont(tempFontFile.getAbsolutePath()); + fontStream.close(); + tempFontFile.deleteOnExit(); + } else { + log.error("폰트 파일을 찾을 수 없습니다: " + fontPath); + } + properties.setFontProvider(fontProvider); + + // 원본 HTML에서 내용을 추출 + String headHtml = doc.select("head").outerHtml(); // 의 전체 내용을 가져옵니다. + + PdfMerger merger = new PdfMerger(pdfDoc); // 최종 문서에 병합할 PdfMerger 생성 + // arrSection의 각 섹션을 PDF에 추가 + for (String section : arrSection) { + if (section != null && !"".equals(section)) { + Elements eSections = doc.select(section); // 섹션 선택 + if (eSections != null && eSections.size() > 0) { + for (Element eSection : eSections) { + + String sectionHtml = + "" + headHtml + "" + eSection.outerHtml() + ""; + + // 각 섹션을 임시 PDF 파일에 변환 + try (ByteArrayOutputStream tempOutputStream = new ByteArrayOutputStream(); + PdfWriter tempWriter = new PdfWriter(tempOutputStream); + PdfDocument tempPdfDoc = new PdfDocument(tempWriter)) { + + HtmlConverter.convertToPdf( + new ByteArrayInputStream(sectionHtml.getBytes(StandardCharsets.UTF_8)), + tempPdfDoc, + properties); + + // tempPdfDoc을 메인 pdfDoc에 병합 + PdfDocument tempDoc = + new PdfDocument( + new PdfReader(new ByteArrayInputStream(tempOutputStream.toByteArray()))); + merger.merge(tempDoc, 1, tempDoc.getNumberOfPages()); + tempDoc.close(); + + // 마지막 섹션이 아닐 경우 새 페이지 추가 + if (!eSection.equals(eSections.last())) { + pdfDoc.addNewPage(); + } + } + } // for + } // if + } // if + } + pdfDoc.close(); // 전체 PDF 문서 작성이 끝난 후에 한번만 닫기 + os.flush(); + } catch (Exception e) { + log.error("PDF 생성 중 오류 발생: " + e.getMessage(), e); + if (!response.isCommitted()) { + response.reset(); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "PDF 생성 중 오류가 발생했습니다."); + } + } + } +} diff --git a/src/main/resources/mappers/displayItem/displayItemMapper.xml b/src/main/resources/mappers/displayItem/displayItemMapper.xml index bd772924..79ba7b2f 100644 --- a/src/main/resources/mappers/displayItem/displayItemMapper.xml +++ b/src/main/resources/mappers/displayItem/displayItemMapper.xml @@ -50,6 +50,7 @@ FROM ( SELECT /* 전체 목록중 미표시 제품 */ MI.ITEM_ID + , MI.ITEM_NO , MI.ITEM_NAME , MI.DISP_ORDER FROM M_ITEM MI @@ -66,6 +67,7 @@ SELECT /* 미표시 제품 중 판매점만 표시 */ MI.ITEM_ID + , MI.ITEM_NO , MI.ITEM_NAME , MI.DISP_ORDER FROM M_SALES_STORE_DISP_ITEM SSDI @@ -84,10 +86,11 @@ /* sqlid : com.interplug.qcast.displayItem.getItemDetail */ SELECT MI.ITEM_ID + , MI.ITEM_NO , MI.ITEM_NAME , MI.GOODS_NO , MI.UNIT - , MI.SPECIFICATION + , MI.PNOW_W AS SPECIFICATION , MI.PNOW_W , MI.ITEM_GROUP , MI.MODULE_FLG diff --git a/src/main/resources/template/excel/excel_download_quotation_detail_template.xlsx b/src/main/resources/template/excel/excel_download_quotation_detail_template.xlsx new file mode 100644 index 00000000..393ccca5 Binary files /dev/null and b/src/main/resources/template/excel/excel_download_quotation_detail_template.xlsx differ diff --git a/src/main/resources/template/pdf/BIZUDPGothic-Regular.ttf b/src/main/resources/template/pdf/BIZUDPGothic-Regular.ttf new file mode 100644 index 00000000..8c2bbdc2 Binary files /dev/null and b/src/main/resources/template/pdf/BIZUDPGothic-Regular.ttf differ diff --git a/src/main/resources/template/pdf/pdf_download_quotation_detail_template.html b/src/main/resources/template/pdf/pdf_download_quotation_detail_template.html new file mode 100644 index 00000000..d3210f56 --- /dev/null +++ b/src/main/resources/template/pdf/pdf_download_quotation_detail_template.html @@ -0,0 +1,255 @@ + + + + + + +
1111111111
+
222222222
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
物件番号作成日
都道府県日射量観測地点
システム容量年間予測発電量
積雪条件風速条件
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
1月2月3月4月5月6月7月8月9月10月11月12月
+
+
+ ● 予測発電量[kWh] +
+ + + + + + + + + + + + + + + + + + + + + + +
1月2月3月4月5月6月7月8月9月10月11月12月合計
+ + + + + + + + + + + + +
屋根面傾斜角方位角(度)太陽電池モジュール枚数
+ + + + + + + + + +
パワーコンディショナー
+
+ ● Hanwha Japan 年間発電量シミュレーション案内事項 +
+
+
+
+
+ + \ No newline at end of file