From 453fece707fc1e611554fbb10125383eba01449a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 18 九月 2026 14:39:09 +0800
Subject: [PATCH] feat(mes): 添加产品检验单Word导出功能

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/indicatorresult/MesQcIndicatorResultController.java |  138 +++++++++++++++++++++++++++++++++++++++------
 1 files changed, 118 insertions(+), 20 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/indicatorresult/MesQcIndicatorResultController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/indicatorresult/MesQcIndicatorResultController.java
index 6591f43..6a8021e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/indicatorresult/MesQcIndicatorResultController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/indicatorresult/MesQcIndicatorResultController.java
@@ -1,8 +1,10 @@
 package cn.iocoder.yudao.module.mes.controller.admin.qc.indicatorresult;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.qc.indicatorresult.vo.*;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
@@ -13,8 +15,11 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcLineDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcLineDO;
 import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.qc.MesQcIndicatorItemTypeEnum;
 import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
 import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcProductReportWordBuilder;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcReportService;
 import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcLineService;
 import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcLineService;
 import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcLineService;
@@ -24,12 +29,15 @@
 import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -44,6 +52,8 @@
 
     @Resource
     private MesQcIndicatorResultService resultService;
+    @Resource
+    private MesQcReportService reportService;
     @Resource
     private MesQcIqcLineService iqcLineService;
     @Resource
@@ -117,12 +127,52 @@
         return success(voPageResult);
     }
 
+    @GetMapping("/report")
+    @Operation(summary = "鑾峰緱妫�楠屾姤鍛婂崟鏁版嵁锛堟墦鍗扮敤锛氳〃澶� + 妫�楠岄」鏍� + 骞宠娴嬮噺 + 绛惧瓧鍖猴級")
+    @Parameters({
+            @Parameter(name = "qcId", description = "璐ㄦ鍗旾D", required = true, example = "1"),
+            @Parameter(name = "qcType", description = "璐ㄦ绫诲瀷", required = true, example = "1"),
+            @Parameter(name = "resultId", description = "妫�楠岀粨鏋滐紙鏍峰搧锛夌紪鍙凤紝鍙笉浼狅紱澶氭牱鍝佹椂鐢ㄤ簬閫夋嫨", example = "1")
+    })
+    @PreAuthorize("@ss.hasPermission('mes:qc-iqc:query')")
+    public CommonResult<MesQcQcReportRespVO> getQcReportData(
+            @RequestParam("qcId") Long qcId,
+            @RequestParam("qcType") Integer qcType,
+            @RequestParam(value = "resultId", required = false) Long resultId) {
+        return success(reportService.getQcReportData(qcId, qcType, resultId));
+    }
+
+    @GetMapping("/export-product-word")
+    @Operation(summary = "瀵煎嚭浜у搧妫�楠屽崟 Word锛堢焊璐ㄦ楠屽崟鐗堝紡锛氭棤鎶�鏈寚鏍囧垪锛屽垽瀹氱粨璁�/璐ㄦ涓撶敤绔犵暀绌烘墜濉紝鎵撳嵃鐢� Word 鎵撳紑锛�")
+    @Parameters({
+            @Parameter(name = "qcId", description = "璐ㄦ鍗旾D", required = true, example = "1"),
+            @Parameter(name = "qcType", description = "璐ㄦ绫诲瀷", required = true, example = "1"),
+            @Parameter(name = "resultId", description = "妫�楠岀粨鏋滐紙鏍峰搧锛夌紪鍙凤紝鍙笉浼狅紱澶氭牱鍝佹椂鐢ㄤ簬閫夋嫨", example = "1")
+    })
+    @PreAuthorize("@ss.hasAnyPermissions('mes:qc-iqc:query', 'mes:qc-ipqc:query', 'mes:qc-oqc:query', 'mes:qc-rqc:query')")
+    public void exportProductWord(
+            @RequestParam("qcId") Long qcId,
+            @RequestParam("qcType") Integer qcType,
+            @RequestParam(value = "resultId", required = false) Long resultId,
+            HttpServletResponse response) throws IOException {
+        MesQcQcReportRespVO report = reportService.getQcReportData(qcId, qcType, resultId);
+        byte[] docx = MesQcProductReportWordBuilder.build(report);
+        String productName = StrUtil.isNotBlank(report.getItemName()) ? report.getItemName() : "浜у搧";
+        String filename = productName + "浜у搧璐ㄩ噺妫�楠屽崟"
+                + (StrUtil.isNotBlank(report.getDocCode()) ? "-" + report.getDocCode() : "") + ".docx";
+        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+        response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
+        response.getOutputStream().write(docx);
+    }
+
     // ==================== 鎷兼帴 VO ====================
 
     /**
-     * 鏋勫缓鏄庣粏 Item 鍒楄〃
+     * 鏋勫缓鏄庣粏 Item 鏍戯紙鏀寔鍒嗙粍灞傜骇 + 骞宠娴嬮噺澶氭壒娆★級
      *
-     * <p>鍓嶇琛ㄥ崟浠呴渶锛歩ndicatorId銆乮ndicatorName銆乿alueType銆乿alueSpecification + 宸叉湁缁撴灉锛坕d銆乿alue銆乺emark锛�</p>
+     * <p>杩斿洖鏍戝舰鍒楄〃锛氶《绾т负鍒嗙粍椤规垨鐙珛褰曞叆椤癸紝鍒嗙粍椤� children 鎸傚瓙椤癸紱
+     * 姣忎釜褰曞叆椤圭殑骞宠娴嬮噺鍊兼斁鍦� measures锛堟寜 measureNo 鍗囧簭锛夛紱
+     * 鍏煎瀛楁 id/value/remark 鍙栫涓�鎵瑰�硷紝渚涙棫鏁版嵁鐩存帴灞曠ず</p>
      *
      * @param details 宸叉湁缁撴灉鏄庣粏锛堢┖鍒楄〃琛ㄧず鑾峰彇绌哄�兼ā鏉匡級
      * @param qcId    璐ㄦ鍗� ID
@@ -155,30 +205,78 @@
             return Collections.emptyList();
         }
 
-        // 1.2 鎵归噺鏌ヨ妫�娴嬫寚鏍�
+        // 1.2 鎵归噺鏌ヨ妫�娴嬫寚鏍囷紱瀛愰」鐨勭埗鍒嗙粍鏈寕鍦ㄨ涓婃椂鑷姩琛ュ叏锛屼繚璇佹爲瀹屾暣
         Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap(new HashSet<>(indicatorIds));
-        // 1.3 鏋勫缓宸叉湁鏄庣粏 Map锛堟寜 indicatorId 绱㈠紩锛�
-        Map<Long, MesQcIndicatorResultDetailDO> detailMap = CollUtil.isEmpty(details)
-                ? Collections.emptyMap()
-                : convertMap(details, MesQcIndicatorResultDetailDO::getIndicatorId);
+        Set<Long> missingParentIds = indicatorMap.values().stream()
+                .filter(ind -> ind.getParentId() != null && ind.getParentId() > 0
+                        && !indicatorMap.containsKey(ind.getParentId()))
+                .map(MesQcIndicatorDO::getParentId).collect(Collectors.toSet());
+        if (CollUtil.isNotEmpty(missingParentIds)) {
+            indicatorMap.putAll(indicatorService.getIndicatorMap(missingParentIds));
+        }
 
-        // 2. 閬嶅巻鎸囨爣 ID锛岀粍瑁� VO
-        List<MesQcIndicatorResultRespVO.Item> voList = new ArrayList<>(indicatorIds.size());
-        for (Long indicatorId : indicatorIds) {
+        // 1.3 宸叉湁鏄庣粏鍒嗙粍锛坕ndicatorId -> 澶氭壒娆″垪琛級
+        Map<Long, List<MesQcIndicatorResultDetailDO>> detailMap = CollUtil.isEmpty(details)
+                ? Collections.emptyMap()
+                : details.stream().collect(Collectors.groupingBy(MesQcIndicatorResultDetailDO::getIndicatorId));
+
+        // 2. 鏋勫缓鑺傜偣
+        Map<Long, MesQcIndicatorResultRespVO.Item> nodeMap = new LinkedHashMap<>();
+        for (Map.Entry<Long, MesQcIndicatorDO> entry : indicatorMap.entrySet()) {
+            Long indicatorId = entry.getKey();
+            MesQcIndicatorDO indicator = entry.getValue();
             MesQcIndicatorResultRespVO.Item vo = new MesQcIndicatorResultRespVO.Item()
-                    .setIndicatorId(indicatorId);
-            // 鏉ヨ嚜 indicator
-            findAndThen(indicatorMap, indicatorId, indicator -> vo
+                    .setIndicatorId(indicatorId)
                     .setIndicatorName(indicator.getName())
                     .setValueType(indicator.getResultType())
-                    .setValueSpecification(indicator.getResultSpecification()));
-            // 鏉ヨ嚜宸叉湁缁撴灉鏄庣粏锛堝鏈夛級
-            findAndThen(detailMap, indicatorId, detail -> vo
-                    .setId(detail.getId()).setResultId(detail.getResultId())
-                    .setValue(detail.getValue()).setRemark(detail.getRemark()));
-            voList.add(vo);
+                    .setValueSpecification(indicator.getResultSpecification())
+                    .setParentId(Objects.requireNonNullElse(indicator.getParentId(), 0L))
+                    .setItemType(Objects.requireNonNullElse(indicator.getItemType(),
+                            MesQcIndicatorItemTypeEnum.RECORD.getType()))
+                    .setFormulaText(indicator.getFormulaText())
+                    .setUnitText(indicator.getUnitText())
+                    .setSortOrder(Objects.requireNonNullElse(indicator.getSortOrder(), 0));
+            List<MesQcIndicatorResultDetailDO> itemDetails = detailMap.getOrDefault(indicatorId, Collections.emptyList());
+            List<MesQcIndicatorResultRespVO.Measure> measures = new ArrayList<>(itemDetails.size());
+            itemDetails.stream()
+                    .sorted(Comparator.comparing(d -> Objects.requireNonNullElse(d.getMeasureNo(), 1)))
+                    .forEach(detail -> measures.add(new MesQcIndicatorResultRespVO.Measure()
+                            .setDetailId(detail.getId()).setResultId(detail.getResultId())
+                            .setMeasureNo(Objects.requireNonNullElse(detail.getMeasureNo(), 1))
+                            .setValue(detail.getValue()).setRemark(detail.getRemark())));
+            vo.setMeasures(measures);
+            // 鍏煎鏃у瓧娈碉細鍙栫涓�鎵规祴閲忓��
+            if (CollUtil.isNotEmpty(measures)) {
+                MesQcIndicatorResultRespVO.Measure first = measures.get(0);
+                vo.setId(first.getDetailId()).setResultId(first.getResultId())
+                        .setValue(first.getValue()).setRemark(first.getRemark());
+            }
+            nodeMap.put(indicatorId, vo);
         }
-        return voList;
+
+        // 3. 鎸傛爲锛氱埗涓嶅湪闆嗗悎鍐呯殑瀛愰」鎻愬崌涓洪《绾э紱鍚岀骇鎸夋帓搴忓彿
+        Comparator<MesQcIndicatorResultRespVO.Item> byOrder =
+                Comparator.comparing(MesQcIndicatorResultRespVO.Item::getSortOrder)
+                        .thenComparing(MesQcIndicatorResultRespVO.Item::getIndicatorId);
+        List<MesQcIndicatorResultRespVO.Item> roots = new ArrayList<>();
+        for (MesQcIndicatorResultRespVO.Item node : nodeMap.values()) {
+            MesQcIndicatorResultRespVO.Item parent = node.getParentId() > 0 ? nodeMap.get(node.getParentId()) : null;
+            if (parent == null) {
+                roots.add(node);
+            } else {
+                if (parent.getChildren() == null) {
+                    parent.setChildren(new ArrayList<>());
+                }
+                parent.getChildren().add(node);
+            }
+        }
+        roots.sort(byOrder);
+        roots.forEach(root -> {
+            if (root.getChildren() != null) {
+                root.getChildren().sort(byOrder);
+            }
+        });
+        return roots;
     }
 
 }

--
Gitblit v1.9.3