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 | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 273 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 e38e930..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
@@ -1,5 +1,6 @@
package com.ruoyi.manage.service.impl;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -9,27 +10,49 @@
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.Pictures;
import com.ruoyi.common.core.domain.entity.User;
+import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.QueryWrappers;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.manage.dto.ClientSatisfactionDto;
import com.ruoyi.manage.mapper.ClientSatisfactionAnalyseFileMapper;
+import com.ruoyi.manage.mapper.ClientSatisfactionAttachmentMapper;
import com.ruoyi.manage.mapper.ClientSatisfactionMapper;
import com.ruoyi.manage.pojo.ClientSatisfaction;
import com.ruoyi.manage.pojo.ClientSatisfactionAnalyseFile;
+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;
/**
@@ -43,6 +66,9 @@
@Resource
private ClientSatisfactionAnalyseFileMapper clientSatisfactionAnalyseFileMapper;
+
+ @Resource
+ private ClientSatisfactionAttachmentMapper clientSatisfactionAttachmentMapper;
@Resource
private UserMapper userMapper;
@@ -169,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 瑕佷慨鏀瑰鎴锋弧鎰忓害鐨勭姸鎬佸璞�
@@ -184,5 +386,76 @@
clientSatisfaction.setConfirmStatus(1);
baseMapper.updateById(clientSatisfaction);
}
+
+ /**
+ * 鏌ヨ婊℃剰搴﹂檮浠�
+ *
+ * @param clientSatisfactionId 婊℃剰搴﹁褰昳d
+ * @return
+ */
+ @Override
+ public List<ClientSatisfactionAttachment> selectAttachments(Integer clientSatisfactionId) {
+ return clientSatisfactionAttachmentMapper.selectList(new QueryWrapper<ClientSatisfactionAttachment>().lambda()
+ .eq(ClientSatisfactionAttachment::getClientSatisfactionId, clientSatisfactionId)
+ .orderByAsc(ClientSatisfactionAttachment::getSort, ClientSatisfactionAttachment::getId));
+ }
+
+ /**
+ * 涓婁紶婊℃剰搴﹂檮浠�
+ *
+ * @param clientSatisfactionId 婊℃剰搴﹁褰昳d
+ * @param file
+ * @return
+ */
+ @Override
+ public ClientSatisfactionAttachment uploadAttachment(Integer clientSatisfactionId, MultipartFile file) {
+ if (baseMapper.selectCount(new QueryWrapper<ClientSatisfaction>()
+ .eq("client_satisfaction_id", clientSatisfactionId)) == 0) {
+ throw new BaseException("瀹㈡埛婊℃剰搴﹁皟鏌ヤ笉瀛樺湪");
+ }
+ if (file == null || file.isEmpty()) {
+ throw new BaseException("闄勪欢涓嶈兘涓虹┖");
+ }
+ // 绯荤粺鍏ㄥ眬涓婁紶涓婇檺涓�200MB锛岃繖閲屾彁鍓嶇粰鍑哄弸濂芥彁绀�
+ if (file.getSize() > 200L * 1024 * 1024) {
+ throw new BaseException("闄勪欢涓嶈兘瓒呰繃200MB");
+ }
+ String originalName = file.getOriginalFilename();
+ if (originalName == null || originalName.trim().isEmpty()) {
+ originalName = "鏈懡鍚嶆枃浠�";
+ }
+ // 涓埆娴忚鍣ㄥ彲鑳藉甫涓婅矾寰勫墠缂�锛屽彧淇濈暀鏂囦欢鍚嶆湰韬�
+ originalName = originalName.replaceAll(".*[/\\\\]", "");
+ try {
+ // 鍥剧墖鏀惧浘鐗囩洰褰曪紙/img 闈欐�佽矾寰勶級锛屽叾浣欎换鎰忔牸寮忔斁 word 鐩綍锛�/word 闈欐�佽矾寰勶級
+ boolean image = originalName.matches("(?i).+\\.(jpg|jpeg|png|gif)$");
+ File directory = new File(image ? imgUrl : wordUrl);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+ String fileUrl = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmssSSS")) + "-" + originalName;
+ file.transferTo(new File(directory, fileUrl));
+ ClientSatisfactionAttachment attachment = new ClientSatisfactionAttachment();
+ attachment.setClientSatisfactionId(clientSatisfactionId);
+ attachment.setFileName(originalName);
+ attachment.setFileUrl(fileUrl);
+ attachment.setSort(selectAttachments(clientSatisfactionId).size());
+ clientSatisfactionAttachmentMapper.insert(attachment);
+ return attachment;
+ } catch (IOException e) {
+ throw new BaseException("闄勪欢涓婁紶澶辫触");
+ }
+ }
+
+ /**
+ * 鍒犻櫎婊℃剰搴﹂檮浠�
+ *
+ * @param id 闄勪欢id
+ * @return
+ */
+ @Override
+ public boolean deleteAttachment(Long id) {
+ return clientSatisfactionAttachmentMapper.deleteById(id) > 0;
+ }
}
--
Gitblit v1.9.3