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 54ff3bef..f91ee097 100644 --- a/src/main/java/com/interplug/qcast/biz/pwrGnrSimulation/PwrGnrSimService.java +++ b/src/main/java/com/interplug/qcast/biz/pwrGnrSimulation/PwrGnrSimService.java @@ -2337,7 +2337,7 @@ public class PwrGnrSimService { .mapToInt(Integer::parseInt) // 정수로 변환 .toArray(); - int referenceValue = 300; // table 높이 + int referenceValue = 250; // table 높이 int orgMaxValue = Arrays.stream(onlyData) .max() @@ -2426,9 +2426,11 @@ public class PwrGnrSimService { // 모듈 list if (data.getRoofModuleList() != null && data.getRoofModuleList().size() > 0) { sb = new StringBuilder(); - + int totCnt = 0; for (int i = 0; i < data.getRoofModuleList().size(); i++) { PwrGnrSimRoofResponse listItem = data.getRoofModuleList().get(i); + String tot = StringUtils.defaultString(listItem.getAmount()); + totCnt += Integer.parseInt(tot); sb.append(""); sb.append("" + StringUtils.defaultString(listItem.getRoofSurface()) + ""); sb.append("" + StringUtils.defaultString(listItem.getSlopeAngleTxt()) + ""); @@ -2437,6 +2439,10 @@ public class PwrGnrSimService { sb.append("" + StringUtils.defaultString(listItem.getAmount()) + ""); sb.append(""); } + sb.append(""); + sb.append("合計"); + sb.append("" + StringUtils.defaultString(String.valueOf(totCnt)) + ""); + sb.append(""); elm = doc.getElementById("roofModuleList_detail"); elm.append(sb.toString()); @@ -2461,7 +2467,7 @@ public class PwrGnrSimService { elm = doc.getElementById("guideInfo"); elm.append(data.getGuideInfo()); - + //log.debug("pwrGnrSimPdfHtml ::: {}", doc); return doc; } diff --git a/src/main/java/com/interplug/qcast/util/PdfUtil.java b/src/main/java/com/interplug/qcast/util/PdfUtil.java index da65672a..2420dfd0 100644 --- a/src/main/java/com/interplug/qcast/util/PdfUtil.java +++ b/src/main/java/com/interplug/qcast/util/PdfUtil.java @@ -53,17 +53,124 @@ public class PdfUtil { return doc; } + /** - * PDF 다운로드 + * PDF 다운로드 (섹션별 방향 설정 지원) * * @param request * @param response * @param doc * @param pdfFileName 파일명 * @param arrSection 노출 Section - * @param isLandscape 가로/세로 설정 + * @param isLandscape 가로/세로 설정 (기본값) + * @param sectionOrientation 섹션별 방향 설정 (선택사항) * @throws IOException */ + public static void pdfDownload( + HttpServletRequest request, + HttpServletResponse response, + Document doc, + String pdfFileName, + String[] arrSection, + boolean isLandscape, + java.util.Map sectionOrientation) + throws IOException { + + // 가로 세로 설정 + com.itextpdf.kernel.geom.PageSize pageSize = isLandscape ? com.itextpdf.kernel.geom.PageSize.A4.rotate() : com.itextpdf.kernel.geom.PageSize.A4; + + // 응답에 PDF 설정 + response.setContentType("application/pdf"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + pdfFileName + "\".pdf"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + + try (java.io.OutputStream os = response.getOutputStream(); + com.itextpdf.kernel.pdf.PdfWriter writer = new com.itextpdf.kernel.pdf.PdfWriter(os); + com.itextpdf.kernel.pdf.PdfDocument pdfDoc = new com.itextpdf.kernel.pdf.PdfDocument(writer)) { + + pdfDoc.setDefaultPageSize(pageSize); + + // 폰트 설정 + ConverterProperties properties = new ConverterProperties(); + String fontPath = "template/pdf/BIZUDPGothic-Regular.ttf"; + properties.setFontProvider(setFontProvider(fontPath)); + + // arrSection이 비어 있는 경우 HTML 전체를 변환 + if (arrSection == null || arrSection.length == 0) { + // HTML 전체를 pdfDoc에 직접 변환 + HtmlConverter.convertToPdf( + new java.io.ByteArrayInputStream(doc.html().getBytes(StandardCharsets.UTF_8)), + pdfDoc, + properties); + os.flush(); + } else { + // arrSection이 있는 경우 각 섹션을 개별적으로 변환 후 병합 + String headHtml = doc.select("head").outerHtml(); + + com.itextpdf.kernel.utils.PdfMerger merger = new com.itextpdf.kernel.utils.PdfMerger(pdfDoc); + for (String section : arrSection) { + if (section != null && !"".equals(section)) { + Elements eSections = doc.select(section); + if (eSections != null && eSections.size() > 0) { + for (Element eSection : eSections) { + + // 섹션별 방향 설정 확인 + boolean sectionIsLandscape = isLandscape; // 기본값 + if (sectionOrientation != null && sectionOrientation.containsKey(section)) { + sectionIsLandscape = sectionOrientation.get(section); + } + + // 가로 섹션인 경우 @page CSS를 landscape로 변경 + String sectionHeadHtml = headHtml; + if (sectionIsLandscape) { + sectionHeadHtml = sectionHeadHtml.replaceAll( + "@page\\s*\\{[^}]*\\}", + "@page { size: A4 landscape; margin: 10mm 12mm; }"); + } + + String sectionHtml = + "" + sectionHeadHtml + "" + eSection.outerHtml() + ""; + + // 섹션별 페이지 크기 설정 + com.itextpdf.kernel.geom.PageSize sectionPageSize = sectionIsLandscape ? com.itextpdf.kernel.geom.PageSize.A4.rotate() : com.itextpdf.kernel.geom.PageSize.A4; + + try (java.io.ByteArrayOutputStream tempOutputStream = new java.io.ByteArrayOutputStream(); + com.itextpdf.kernel.pdf.PdfWriter tempWriter = new com.itextpdf.kernel.pdf.PdfWriter(tempOutputStream); + com.itextpdf.kernel.pdf.PdfDocument tempPdfDoc = new com.itextpdf.kernel.pdf.PdfDocument(tempWriter)) { + + tempPdfDoc.setDefaultPageSize(sectionPageSize); + + HtmlConverter.convertToPdf( + new java.io.ByteArrayInputStream(sectionHtml.getBytes(StandardCharsets.UTF_8)), + tempPdfDoc, + properties); + + com.itextpdf.kernel.pdf.PdfDocument tempDoc = + new com.itextpdf.kernel.pdf.PdfDocument( + new com.itextpdf.kernel.pdf.PdfReader(new java.io.ByteArrayInputStream(tempOutputStream.toByteArray()))); + merger.merge(tempDoc, 1, tempDoc.getNumberOfPages()); + tempDoc.close(); + + if (!eSection.equals(eSections.last())) { + pdfDoc.addNewPage(); + } + } + } + } + } + } + pdfDoc.close(); + os.flush(); + } + } catch (Exception e) { + log.error("PDF 생성 중 오류 발생: " + e.getMessage(), e); + if (!response.isCommitted()) { + response.reset(); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "PDF 생성 중 오류가 발생했습니다."); + } + } + } + public static void pdfDownload( HttpServletRequest request, HttpServletResponse response, 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 index 9d808990..23ce1089 100644 --- a/src/main/resources/template/pdf/pdf_download_quotation_detail_template.html +++ b/src/main/resources/template/pdf/pdf_download_quotation_detail_template.html @@ -1,396 +1,403 @@ - + + + + Document + /* @page { size: A4 landscape; margin: 10mm 12mm; } */ + .section1, .section2, .section3, .section4, .section5, .section6{ + page-break-before: always; + page-break-after: always; + page-break-inside: avoid; + + /* 실제 출력 높이: 297mm - 20mm = 277mm */ + max-height: 277mm; + overflow: hidden; + padding: 5mm; + } + .section:first-child { page-break-before: auto; } + @media print { + body { + print-color-adjust: exact; + -webkit-print-color-adjust: exact; + } + } + + + /*.section:first-child {*/ + /* page-break-before: auto;*/ + /*}*/ + body { + margin: 0; + padding: 0; + font-family: 'Malgun Gothic', sans-serif; + font-size: 9px; /* 폰트 작게 */ + line-height: 1.2; /* 줄간격 좁게 */ + } + table { + width: 100%; + table-layout: fixed; + font-family: M-Gothic; + font-size: 9px; + border-collapse: collapse; + margin: 0 auto; + line-height: 115%; + color: #333 + } + + .month-table td { + word-break: break-all; + } + + th, td { + padding: 3px 3px 2px; + text-align: center; + font-size: 9px; + border: 1px solid #000 + } + + th { + background-color: #eee; + font-weight: bold + } + + td { + background-color: white + } + + .col-20 { + width: 20% + } + + .col-15 { + width: 15% + } + + + .mb20 { + margin-bottom: 20px + } + + .al-l { + text-align: left !important + } + + .al-r { + text-align: right !important + } + + /* 가이드박스 */ + .guide-box { + position: relative; + border: 1px solid #000; + padding: 15px; + margin: 10px 0; + display: flex; + align-items: flex-start; + } + + .guide-image { + margin-right: 15px; + flex-shrink: 0; + } + + .guide-content { + white-space: pre-line; + line-height: 1.2; + font-size: 9px; + flex: 1; + } + + /* 차트퍼블 */ + .chart-wrapper { + width: 100%; + table-layout: fixed; + margin: 10px 0; + height: 279.3px + } + + .chart-wrapper td { + padding: 0; + border: none + } + + .chart-wrapper td.y-axis-wrap { + vertical-align: bottom + } + + .y-axis { + margin-top: -5px + } + + .y-axis td { + font-size: 10px; + line-height: 1; + vertical-align: bottom; + padding: 10.3px 0 + } + + .y-axis td.zero { + padding-bottom: 0 + } + + .y-axis td.top { + padding-top: 0 + } + + .chart-wrapper td.bar-cell { + position: relative; + vertical-align: bottom; + border-bottom: 1px solid #ddd; + background-color: transparent + } + + .chart-wrapper td.bar-cell .bar { + position: absolute; + bottom: 0; + left: 13.4px; + width: 30px; + margin: 0 auto + } + + .chart-wrapper td.month-cell { + font-size: 10px; + padding-top: 7px + } + + .chart-line td { + background-color: transparent; + padding: 15.2px 0; + border-bottom: 1px solid #ddd !important + } + + .chart-line td.top { + padding: 1px + } + + /* 타이틀 */ + .title { + text-align: center; + font-size: 30px; + letter-spacing: 10px; + font-weight: bold; + margin: 10px 0 30px; + border-top: 1px solid #000; + border-bottom: 1px solid #000; + } + + /* 비고퍼블 */ + .note th, .note td { + padding: 10px + } + + /* 총액퍼블 */ + .all-price-wrap { + text-align: center; + margin-bottom: 20px; + } + + .all-price { + display: inline-block; + text-align: center; + border-bottom: 1px solid #000; + } + + .all-price span { + display: inline-block; + font-size: 14px; + font-weight: bold; + } + + .all-price-value { + width: 300px; + display: inline-block; + text-align: right; + } + + /* 태양전지 */ + .sun-volt { + display: inline-block; + margin-bottom: 20px; + border-bottom: 1px solid #000; + } + + .sun-volt span { + display: inline-block; + font-size: 11px; + font-weight: bold; + } + + .sun-volt-value { + width: 130px; + display: inline-block; + text-align: right; + font-size: 11px; + } + + .price-table td.end { + background-color: #eee; + font-weight: bold; + } + + .product-info-wrap { + table-layout: fixed; + margin-bottom: 25px; + } + + .product-info-wrap td { + border: none; + padding: 0; + text-align: left; + vertical-align: top; + } + + /* 견적서 정의 */ + .estimate-wrap { + display: inline-block; + vertical-align: top; + } + + .estimate-info { + display: inline-block; + margin-bottom: 40px; + border-bottom: 1px solid #000; + } + + .estimate-info span { + font-size: 11px; + font-weight: bold; + } + + .estimate-info-name { + padding-right: 20px; + } + + /* tit-form */ + .estimate-tit-form { + display: block; + margin-bottom: 30px; + } + + .estimate-tit-form span { + font-size: 11px; + border-bottom: 1px solid #000; + } + + .estimate-tit-form .estimate-tit { + display: inline-block; + width: 82px; + font-size: 10px; + font-weight: bold; + border: none; + text-align: right; + } + + /* 물품 */ + .product-wrap > div { + margin-bottom: 10px; + } + + .product-num { + display: inline-block; + margin-right: 20px; + } + + .product-count { + display: inline-block;; + } + + .product-count .tit, .product-num .tit { + display: inline-block; + font-size: 10px; + font-weight: bold; + } + + .product-count span, .product-num span { + display: inline-block; + font-size: 11px; + } + + .product-form { + display: block; + } + + .product-form .tit { + display: inline-block; + width: 62px; + font-size: 11px; + font-weight: bold; + border: none; + text-align: right; + } + + .product-form span { + display: inline-block; + font-size: 11px; + } + + .store-info-tit { + font-size: 11px; + font-weight: bold; + display: block; + margin-bottom: 10px; + } + + .product-info-wrap .store-info { + display: block; + margin-bottom: 25px; + } + + .store-info span { + display: block; + font-size: 10px; + margin-bottom: 0px; + } + + .store-info .number { + display: block; + font-size: 11px; + } + + .store-info .number span { + display: inline-block; + } + + .approval-table { + width: 200px; + margin: 0 0 0 auto; + } + + .approval-table th { + background-color: transparent; + } + + .approval-table td { + padding: 30px 0; + border: 1px solid #000; + } + .guide-image{ + position: absolute; + top: 20px; + left: 20px; + width: 50px; + } + + .guide-img-tit{ + text-align: center; + font-size: 8px; + } + .section1, .section2, .section6{ + max-width: 740px; + margin: 0 auto; + } + .section3, .section4, .section5 { + max-width: 1040px; + margin: 0 auto; + } + + +
@@ -483,13 +490,13 @@ - + - - - - - + + + + + @@ -564,318 +571,343 @@ + + +
NoNo 品名型番単価数量単位価格型番単価数量単位価格
作成日
+ + - - - - - - - - - - - - - - - - + +
都道府県日射量観測地点
システム容量年間予測発電量
積雪条件風速条件 +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
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 年間発電量シミュレーション案内事項 +
+
+
+                
+
+
+ + + + + + + + + + + + + + + + + + + +
システム容量
年間予測発電量都道府県
日射量観測地点積雪量
+ + + + + + + + + + + + +
屋根面傾斜角度方位角(度)太陽電池モジュール枚数(枚)
+ + + + + + + + + +
パワーコンディショナー
+
-
- - - - - - - - - - - - - - - - - - - - + +
+
- - - -
-
1月2月3月4月5月6月7月8月9月10月11月12月
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
物件番号作成日
物件名積雪条件
モジュール容量認定容量風速条件
都道府県日射量観測地点
+ + + + + + + +
+
+
+
+ +
+
+
+
+
+                ■注意事項
+                 ・本図面は見積作成の為、太陽電池モジュールの配列及び枚数、架台のレールの種類数量、
+                  および、支持点数を示すものであり、実際の施工においては、現地調査で支持点の位置を確認し、
+                  施工マニュアルに従って施工して下さい。
+
+                 ・各屋根材の動き流れ寸法は下記と仮定して作図しております。
+                  実際の寸法を確認のうえ、施工マニュアルに従った施工を行って下さい。
+                  和瓦53A:235㎜、和瓦53B:225㎜、平板瓦(C、D型):280㎜、S瓦:260㎜、セメント瓦:345㎜、スレート:182㎜
+                  アスファルトシングル:143㎜、金属横葺:182㎜
+
+                ・設置可能地域であっても、錆やよごれ等による外観について保証するものではありません。
+                ・垂直積雪量は特定行政庁の判断により更新される場合があります。設置される地域の特定行政庁への確認を必ず行って下さい。
+            
+
+ + + + + + + + + + + + + + + +
屋根面屋根材種類勾配施工レベル施工方法面粗度区分設置高さ
+
+ + + + + + + + +
No部材名数量
- -
- ● 予測発電量[kWh] -
- - - - - - - - - - - - - - - - - - - - - - -
1月2月3月4月5月6月7月8月9月10月11月12月合計
+ + + + + + + + + +
Noパワーコンディショナ型式
- - - - - - - - - - - - -
屋根面傾斜角度方位角(度)太陽電池モジュール枚数(枚)
- - - - - - - - - -
パワーコンディショナー
-
- ● Hanwha Japan 年間発電量シミュレーション案内事項 -
-
-
-        
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
物件番号作成日
物件名積雪条件
モジュール容量認定容量風速条件
都道府県日射量観測地点
- -
-
-
- -
- -
-
- - - - - - - - - - - -
No部材名数量
- - - - - - - - - - -
Noパワーコンディショナ型式
- - - - - - - - - - - - - - - - - -
屋根面勾配設置枚数容量(kW)
合計
- -
-
-            ■注意事項
-             ・本図面は見積作成の為、太陽電池モジュールの配列及び枚数、架台のレールの種類数量、
-              および、支持点数を示すものであり、実際の施工においては、現地調査で支持点の位置を確認し、
-              施工マニュアルに従って施工して下さい。
-
-             ・各屋根材の動き流れ寸法は下記と仮定して作図しております。
-              実際の寸法を確認のうえ、施工マニュアルに従った施工を行って下さい。
-              和瓦53A:235㎜、和瓦53B:225㎜、平板瓦(C、D型):280㎜、S瓦:260㎜、セメント瓦:345㎜、スレート:182㎜
-              アスファルトシングル:143㎜、金属横葺:182㎜
-
-            ・設置可能地域であっても、錆やよごれ等による外観について保証するものではありません。
-            ・垂直積雪量は特定行政庁の判断により更新される場合があります。設置される地域の特定行政庁への確認を必ず行って下さい。
-        
-
- - - - - - - - - - - - - - - -
屋根面屋根材種類勾配施工レベル施工方法面粗度区分設置高さ
+ + + + + + + + + + + + + + + + +
屋根面勾配設置枚数容量(kW)
合計ssss
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
物件番号作成日
物件名積雪条件
モジュール容量認定容量風速条件
都道府県日射量観測地点
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
物件番号作成日
物件名積雪条件
モジュール容量認定容量風速条件
都道府県日射量観測地点
-
-
+ + + + + + + +
+
+
+
+
+
+
+
+
+                ■注意事項
+                 ・本図面は見積作成の為、太陽電池モジュールの配列及び枚数、架台のレールの種類数量、
+                  および、支持点数を示すものであり、実際の施工においては、現地調査で支持点の位置を確認し、
+                  施工マニュアルに従って施工して下さい。
+
+                 ・各屋根材の動き流れ寸法は下記と仮定して作図しております。
+                  実際の寸法を確認のうえ、施工マニュアルに従った施工を行って下さい。
+                  和瓦53A:235㎜、和瓦53B:225㎜、平板瓦(C、D型):280㎜、S瓦:260㎜、セメント瓦:345㎜、スレート:182㎜
+                  アスファルトシングル:143㎜、金属横葺:182㎜
+
+                ・設置可能地域であっても、錆やよごれ等による外観について保証するものではありません。
+                ・垂直積雪量は特定行政庁の判断により更新される場合があります。設置される地域の特定行政庁への確認を必ず行って下さい。
+            
-
-
- - - - - - - - - - - - -
No部材名数量
- -
-
-            ■注意事項
-             ・本図面は見積作成の為、太陽電池モジュールの配列及び枚数、架台のレールの種類数量、
-              および、支持点数を示すものであり、実際の施工においては、現地調査で支持点の位置を確認し、
-              施工マニュアルに従って施工して下さい。
-
-             ・各屋根材の動き流れ寸法は下記と仮定して作図しております。
-              実際の寸法を確認のうえ、施工マニュアルに従った施工を行って下さい。
-              和瓦53A:235㎜、和瓦53B:225㎜、平板瓦(C、D型):280㎜、S瓦:260㎜、セメント瓦:345㎜、スレート:182㎜
-              アスファルトシングル:143㎜、金属横葺:182㎜
-
-            ・設置可能地域であっても、錆やよごれ等による外観について保証するものではありません。
-            ・垂直積雪量は特定行政庁の判断により更新される場合があります。設置される地域の特定行政庁への確認を必ず行って下さい。
-        
-
- - - - - - - - - - - - - - - - -
屋根面屋根材種類勾配施工レベル施工方法面粗度区分設置高さ
+ + + + + + + + + + + + + + +
屋根面屋根材種類勾配施工レベル施工方法面粗度区分設置高さ
+
+ + + + + + + + + + +
No部材名数量
+
- +
重量算出シート @@ -901,8 +933,8 @@
- システム重量合計: kg -
+ システム重量合計: kg +
diff --git a/src/main/resources/template/pdf/pdf_download_quotation_detail_template2.html b/src/main/resources/template/pdf/pdf_download_quotation_detail_template2.html index 03c75132..f6734593 100644 --- a/src/main/resources/template/pdf/pdf_download_quotation_detail_template2.html +++ b/src/main/resources/template/pdf/pdf_download_quotation_detail_template2.html @@ -1,9 +1,12 @@ - + + + + Document - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -516,117 +409,126 @@ - - - - - - - - - - - - - - - - - - +
作成日
都道府県日射量観測地点
システム容量年間予測発電量
積雪条件風速条件
-
- ● -
-
- - - - +
- - +
+ + + - - - - - - - - - - - - - - - - + +
+ ● 予測発電量[kWh] +
- -
+
+ + + + + + + + + + + + + + + + + + + + +
+ + + +
+
1月2月3月4月5月6月7月8月9月10月11月12月
-
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月合計
+ + + + + + + + + + + + + + + + + + + + + +
1月2月3月4月5月6月7月8月9月10月11月12月合計
+
+ ● Hanwha Japan 年間発電量シミュレーション案内事項 +
+
+
+                
+
+
+ + + + + + + + + + + + + + + + + + + +
システム容量
年間予測発電量都道府県
日射量観測地点積雪量
+ + + + + + + + + + + + +
屋根面傾斜角度方位角(度)太陽電池モジュール枚数(枚)
+ + + + + + + + + +
パワーコンディショナー
+
- - - - - - - - - - - - - -
屋根面傾斜角度方位角(度)太陽電池モジュール枚数(枚)
- - - - - - - - - -
パワーコンディショナー
-
- ● Hanwha Japan 年間発電量シミュレーション案内事項 -
-
-
-        
-
\ No newline at end of file