From 647960c44d36b97263fee0325487f9dea8b24307 Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期五, 30 十月 2026 16:32:18 +0800
Subject: [PATCH] feat(cnas,inspect): 客户满意度表单与年度汇总导出、委托单费用优惠维护

---
 cnas-manage/src/main/java/com/ruoyi/manage/service/impl/ClientSatisfactionServiceImpl.java |  193 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 193 insertions(+), 0 deletions(-)

diff --git a/cnas-manage/src/main/java/com/ruoyi/manage/service/impl/ClientSatisfactionServiceImpl.java b/cnas-manage/src/main/java/com/ruoyi/manage/service/impl/ClientSatisfactionServiceImpl.java
index 8076ea0..5abe5b4 100644
--- a/cnas-manage/src/main/java/com/ruoyi/manage/service/impl/ClientSatisfactionServiceImpl.java
+++ b/cnas-manage/src/main/java/com/ruoyi/manage/service/impl/ClientSatisfactionServiceImpl.java
@@ -22,20 +22,37 @@
 import com.ruoyi.manage.pojo.ClientSatisfactionAttachment;
 import com.ruoyi.manage.service.ClientSatisfactionService;
 import com.ruoyi.system.mapper.UserMapper;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFRun;
+import org.apache.poi.xwpf.usermodel.XWPFTable;
+import org.apache.poi.xwpf.usermodel.XWPFTableCell;
+import org.apache.poi.xwpf.usermodel.XWPFTableRow;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URLEncoder;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 
 
 /**
@@ -178,6 +195,182 @@
     }
 
     /**
+     * 瀹㈡埛婊℃剰搴﹀勾搴︽眹鎬诲鍑�
+     *
+     * @param year     姹囨�诲勾搴�
+     * @param response
+     */
+    @Override
+    public void exportAnnualSummary(Integer year, HttpServletResponse response) {
+        int summaryYear = year == null ? LocalDate.now().getYear() : year;
+        List<ClientSatisfaction> list = baseMapper.selectList(new QueryWrapper<ClientSatisfaction>().lambda()
+                .ge(ClientSatisfaction::getFillDate, LocalDate.of(summaryYear, 1, 1))
+                .le(ClientSatisfaction::getFillDate, LocalDate.of(summaryYear, 12, 31))
+                .isNotNull(ClientSatisfaction::getTotalScore)
+                .orderByAsc(ClientSatisfaction::getFillDate)
+                .orderByAsc(ClientSatisfaction::getClientSatisfactionId));
+        if (list.isEmpty()) {
+            throw new ErrorException(summaryYear + "骞村害鏆傛棤婊℃剰搴﹁褰�");
+        }
+        InputStream inputStream = this.getClass().getResourceAsStream("/static/customer-satisfaction-annual-summary.docx");
+        if (inputStream == null) {
+            throw new ErrorException("骞村害姹囨�绘ā鏉夸笉瀛樺湪");
+        }
+        try (InputStream is = inputStream) {
+            XWPFDocument loaded = new XWPFDocument(is);
+            XWPFDocument document = fillAnnualSummaryTable(loaded, summaryYear, list);
+            try (XWPFDocument doc = document) {
+                response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+                String fileName = URLEncoder.encode("瀹㈡埛婊℃剰搴﹀勾搴︽眹鎬昏〃_" + summaryYear + "骞�.docx", "UTF-8");
+                response.setHeader("Content-disposition", "attachment;filename=" + fileName);
+                try (OutputStream os = response.getOutputStream()) {
+                    doc.write(os);
+                    os.flush();
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException("瀵煎嚭澶辫触");
+        }
+    }
+
+    /**
+     * 鎸夊勾搴︽眹鎬绘ā鏉垮~鍏呮槑缁嗕笌缁熻鏁版嵁
+     */
+    private XWPFDocument fillAnnualSummaryTable(XWPFDocument document, int year, List<ClientSatisfaction> list) throws IOException {
+        XWPFTable table = document.getTables().get(0);
+
+        // 妯℃澘鍥哄畾姣忛〉 17 琛屾槑缁嗭細涓嶈冻涓嶅垹绌鸿锛岃秴杩囧垯鍙﹁捣涓�椤碉紝閲嶅鏍囬涓庤〃澶村悗缁х画濉�
+        int dataStart = 3;
+        int templateRows = 17;
+        int total = list.size();
+        int pages = Math.max(1, (total + templateRows - 1) / templateRows);
+        if (pages > 1) {
+            // 鍦� XML 灞傛彃鍏ョ画椤佃锛涙彃鍏ュ悗闇�閲嶆柊鍔犺浇鏂囨。锛孭OI 鐨勮/鍗曞厓鏍煎寘瑁呯被鎵嶄細鍒锋柊
+            CTTbl ctTbl = table.getCTTbl();
+            CTRow titleRow = table.getRow(0).getCtRow();
+            CTRow headerRow1 = table.getRow(1).getCtRow();
+            CTRow headerRow2 = table.getRow(2).getCtRow();
+            CTRow dataRowSample = table.getRow(dataStart + templateRows - 1).getCtRow();
+            int insertPos = dataStart + templateRows;
+            for (int page = 1; page < pages; page++) {
+                CTRow newTitleRow = ctTbl.insertNewTr(insertPos++);
+                newTitleRow.set(titleRow.copy());
+                removeBookmarks(newTitleRow);
+                pageBreakBefore(newTitleRow);
+                CTRow newHeader1 = ctTbl.insertNewTr(insertPos++);
+                newHeader1.set(headerRow1.copy());
+                CTRow newHeader2 = ctTbl.insertNewTr(insertPos++);
+                newHeader2.set(headerRow2.copy());
+                for (int r = 0; r < templateRows; r++) {
+                    CTRow newRow = ctTbl.insertNewTr(insertPos++);
+                    newRow.set(dataRowSample.copy());
+                }
+            }
+            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+            document.write(buffer);
+            document.close();
+            document = new XWPFDocument(new ByteArrayInputStream(buffer.toByteArray()));
+            table = document.getTables().get(0);
+        }
+
+        setCellText(table.getRow(0).getCell(0), "姹囨�诲勾搴︼細" + year + "骞�");
+        for (int page = 1; page < pages; page++) {
+            setCellText(table.getRow(page * (templateRows + 3)).getCell(0), "姹囨�诲勾搴︼細" + year + "骞�");
+        }
+
+        int satisfied = 0;
+        for (int i = 0; i < total; i++) {
+            ClientSatisfaction item = list.get(i);
+            boolean isSatisfied = item.getSatisfactionResult() != null
+                    ? item.getSatisfactionResult() == 0
+                    : item.getTotalScore() != null && item.getTotalScore() >= 80;
+            if (isSatisfied) {
+                satisfied++;
+            }
+            int pageIndex = i / templateRows;
+            int indexInPage = i % templateRows;
+            XWPFTableRow row = table.getRow(dataStart + pageIndex * (templateRows + 3) + indexInPage);
+            setCellText(row.getCell(0), String.valueOf(i + 1));
+            setCellText(row.getCell(1), item.getUnitName());
+            setCellText(row.getCell(2), isSatisfied ? "鈭�" : "");
+            setCellText(row.getCell(3), isSatisfied ? "" : "鈭�");
+            String reason = StringUtils.isNotBlank(item.getLabOpinion()) ? item.getLabOpinion() : item.getRemark();
+            setCellText(row.getCell(4), isSatisfied ? "" : reason);
+        }
+
+        XWPFTableRow statRow = table.getRow(dataStart + (pages - 1) * (templateRows + 3) + templateRows + 2);
+        setCellText(statRow.getCell(0), "鍏� " + total + " 浠�");
+        setCellText(statRow.getCell(1), "鍏� " + satisfied + " 浠�");
+        setCellText(statRow.getCell(2), "鍏� " + (total - satisfied) + " 浠�");
+        String percent = String.format(Locale.ROOT, "%.1f", satisfied * 100.0 / total);
+        if (percent.endsWith(".0")) {
+            percent = percent.substring(0, percent.length() - 2);
+        }
+        setCellText(statRow.getCell(3), percent + "%");
+        return document;
+    }
+
+    /**
+     * 缁〉棣栬璁剧疆娈靛墠鍒嗛〉锛屼繚璇佹瘡椤垫牸瀛愭暟鍥哄畾
+     */
+    private void pageBreakBefore(CTRow row) {
+        if (row.sizeOfTcArray() == 0) {
+            return;
+        }
+        CTTc cell = row.getTcArray(0);
+        if (cell.sizeOfPArray() == 0) {
+            return;
+        }
+        CTP paragraph = cell.getPArray(0);
+        CTPPr pPr = paragraph.isSetPPr() ? paragraph.getPPr() : paragraph.addNewPPr();
+        if (!pPr.isSetPageBreakBefore()) {
+            pPr.addNewPageBreakBefore();
+        }
+    }
+
+    /**
+     * 鍒犻櫎鍏嬮殕琛屽唴鐨勪功绛撅紝閬垮厤鏂囨。鍑虹幇閲嶅涔︾
+     */
+    private void removeBookmarks(CTRow row) {
+        removeBookmarkNodes(row.getDomNode());
+    }
+
+    private void removeBookmarkNodes(Node node) {
+        if (node.getNodeType() == Node.ELEMENT_NODE
+                && ("bookmarkStart".equals(node.getLocalName()) || "bookmarkEnd".equals(node.getLocalName()))) {
+            Node parent = node.getParentNode();
+            if (parent != null) {
+                parent.removeChild(node);
+            }
+            return;
+        }
+        NodeList children = node.getChildNodes();
+        for (int i = children.getLength() - 1; i >= 0; i--) {
+            removeBookmarkNodes(children.item(i));
+        }
+    }
+
+    /**
+     * 濉啓鍗曞厓鏍兼枃鏈紝淇濈暀鍘熸湁娈佃惤鏍煎紡
+     */
+    private void setCellText(XWPFTableCell cell, String text) {
+        while (cell.getParagraphs().size() > 1) {
+            cell.removeParagraph(cell.getParagraphs().size() - 1);
+        }
+        XWPFParagraph paragraph = cell.getParagraphs().get(0);
+        for (int i = paragraph.getRuns().size() - 1; i >= 0; i--) {
+            paragraph.removeRun(i);
+        }
+        XWPFRun run = paragraph.createRun();
+        run.setText(text == null ? "" : text);
+        run.setFontFamily("瀹嬩綋");
+        run.setFontFamily("瀹嬩綋", XWPFRun.FontCharRange.eastAsia);
+        run.setFontSize(10.5);
+        cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
+    }
+
+    /**
      * 纭瀹㈡埛婊℃剰搴�
      *
      * @param clientSatisfaction 瑕佷慨鏀瑰鎴锋弧鎰忓害鐨勭姸鎬佸璞�

--
Gitblit v1.9.3