From b43973fb18890173bdb0f973f217df04e18a2095 Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期五, 18 九月 2026 09:08:01 +0800
Subject: [PATCH] fix:1.检验单模板实时更新

---
 inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java | 1310 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 1,118 insertions(+), 192 deletions(-)

diff --git a/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java b/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java
index 31c71a6..cd0ebb6 100644
--- a/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java
+++ b/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java
@@ -45,6 +45,7 @@
 import com.ruoyi.inspect.pojo.*;
 import com.ruoyi.inspect.service.*;
 import com.ruoyi.inspect.util.HackLoopTableRenderPolicy;
+import com.ruoyi.inspect.util.PreserveReportPicturePlaceholderHandler;
 import com.ruoyi.inspect.vo.InsOrderPlanTaskSwitchVo;
 import com.ruoyi.inspect.vo.InsOrderPlanVO;
 import com.ruoyi.inspect.vo.InsSampleUserVO;
@@ -81,6 +82,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * 妫�楠屼换鍔�-涓氬姟瀹炵幇灞�
@@ -90,6 +92,7 @@
 
     private static final int DETECTION_REPORT = 0;
     private static final int INSPECTION_REPORT = 1;
+    private static final String DIRECT_TEMPLATE_LABORATORY = "鏂拌仛";
 
     @Resource
     private InsSampleMapper insSampleMapper;
@@ -153,6 +156,8 @@
     private InsOrderStandardTemplateService insOrderStandardTemplateService;
     @Resource
     private InsOrderDeviceRecordMapper insOrderDeviceRecordMapper;
+    @Resource
+    private InsOrderTemplateDeviceMapper insOrderTemplateDeviceMapper;
     @Resource
     private InsOrderDeviceRecordService insOrderDeviceRecordService;
     @Resource
@@ -221,14 +226,8 @@
 
     @Override
     public Map<String, Object> doInsOrder(Integer id, String laboratory) {
-        InsOrder insOrder = new InsOrder();
-        insOrder.setId(id);
+        ensureDirectTemplateOrderState(id);
         InsOrder order = insOrderMapper.selectById(id);
-        if (BeanUtil.isEmpty(order.getInsTime())) {
-            insOrder.setInsTime(LocalDateTime.now());
-            insOrderMapper.updateById(insOrder);
-            insOrderStateMapper.update(null, Wrappers.<InsOrderState>lambdaUpdate().eq(InsOrderState::getInsOrderId, id).eq(InsOrderState::getLaboratory, laboratory).set(InsOrderState::getInsTime, LocalDateTime.now()).set(InsOrderState::getInsState, 1));
-        }
         Map<String, Object> map = insOrderService.getInsOrderAndSample(id, laboratory);
         List<SampleProductDto> list = JSON.parseArray(JSON.toJSONString(map.get("sampleProduct")), SampleProductDto.class);
         map.put("sampleProduct", list);
@@ -267,55 +266,109 @@
                 insProducts = insSampleMapper.getInsProduct6(dto.getId(), dto.getLaboratory(), dto.getRawMaterialTag());
                 break;
         }
+        // 鏌ヨ璁㈠崟Id
+        InsOrder order = insOrderMapper.selectFirstSubmit(dto.getId());
+        if (order == null) {
+            return BeanUtil.isEmpty(insProducts) ? null : insProducts;
+        }
+        insProducts = mergeDirectTemplateSnapshots(order, dto.getId(), dto.getLaboratory(), insProducts);
         if (BeanUtil.isEmpty(insProducts)) {
             return null;
         }
-        // 鏌ヨ璁㈠崟Id
-        InsOrder order = insOrderMapper.selectFirstSubmit(dto.getId());
-        getTemplateThing(order, insProducts);
+        getTemplateThing(order, insProducts, dto.getLaboratory());
         return insProducts;
+    }
+
+    /**
+     * 鐩寸粦妯℃澘璁㈠崟鐨勯〉绛炬潵婧愭槸璁㈠崟妯℃澘蹇収锛岃�屼笉鏄凡鐢熸垚鐨勬楠岄」銆�
+     *
+     * 浠嶄繚鐣欐瘡涓ā鏉夸笅宸叉湁鐨勬楠岄」璁板綍锛涘鏋滄煇涓ā鏉挎病鏈夆�滄楠岄」鈥濇爣璁帮紝琛ヤ竴鏉′粎鐢ㄤ簬
+     * 鎵胯浇妯℃澘鍐呭鐨勮繑鍥炲璞★紝浣垮墠绔篃鑳藉睍绀恒�佸~鍐欒妯℃澘銆�
+     */
+    private List<InsProduct> mergeDirectTemplateSnapshots(InsOrder order, Integer sampleId,
+                                                           String laboratory, List<InsProduct> products) {
+        List<InsProduct> source = products == null ? new ArrayList<>() : products;
+        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateService.list(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, order.getId())
+                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
+        if (snapshots.isEmpty()) {
+            return source;
+        }
+        InsSample sample = insSampleMapper.selectById(sampleId);
+        boolean directTemplateOrder = source.stream()
+                .anyMatch(product -> product.getTemplateRowIndex() != null)
+                || isDirectTemplateSample(order, sample);
+        if (!directTemplateOrder) {
+            return source;
+        }
+
+        Map<Integer, List<InsProduct>> productsByTemplate = source.stream()
+                .filter(product -> product.getTemplateId() != null)
+                .collect(Collectors.groupingBy(InsProduct::getTemplateId, LinkedHashMap::new, Collectors.toList()));
+        List<InsProduct> result = new ArrayList<>();
+        for (InsOrderStandardTemplate snapshot : snapshots) {
+            List<InsProduct> templateProducts = productsByTemplate.remove(snapshot.getTemplateId());
+            if (CollectionUtils.isNotEmpty(templateProducts)) {
+                templateProducts.forEach(product -> product.setTemplateSort(snapshot.getSort()));
+                result.addAll(templateProducts);
+                continue;
+            }
+            InsProduct templateCarrier = new InsProduct();
+            templateCarrier.setInsSampleId(sampleId);
+            templateCarrier.setTemplateId(snapshot.getTemplateId());
+            templateCarrier.setSonLaboratory(laboratory);
+            templateCarrier.setState(1);
+            templateCarrier.setSort(snapshot.getSort());
+            templateCarrier.setTemplateSort(snapshot.getSort());
+            result.add(templateCarrier);
+        }
+        // 鍏煎鍘嗗彶寮傚父鏁版嵁锛氳鍗曞揩鐓т互澶栫殑妫�楠岄」浠嶇劧杩斿洖锛屼絾鏀惧湪蹇収椤电涔嬪悗銆�
+        for (List<InsProduct> remaining : productsByTemplate.values()) {
+            result.addAll(remaining);
+        }
+        source.stream().filter(product -> product.getTemplateId() == null).forEach(result::add);
+        return result;
+    }
+
+    private boolean isDirectTemplateSample(InsOrder order, InsSample sample) {
+        return sample != null
+                && Objects.equals(sample.getInsOrderId(), order.getId())
+                && sample.getStandardMethodListId() == null
+                && StringUtils.isBlank(sample.getFactory())
+                && StringUtils.isBlank(sample.getSampleType())
+                && StringUtils.isBlank(sample.getModel())
+                && StringUtils.isBlank(order.getFactory())
+                && StringUtils.isBlank(order.getSampleType());
     }
 
     @Override
     public Map<String,Object> checkSubmitPlan(Integer orderId, String laboratory) {
         Map<String, Object> map = new HashMap<>();
         List<String> collect = new ArrayList<>();
-        List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery().eq(InsSample::getInsOrderId, orderId).select(InsSample::getId));
-        List<Integer> ids = insSamples.stream().map(a -> a.getId()).collect(Collectors.toList());
-        List<InsProduct> insProducts = insProductMapper.selectList(Wrappers.<InsProduct>lambdaQuery()
-                .in(InsProduct::getInsSampleId, ids)
-                .eq(InsProduct::getSonLaboratory, laboratory)
-                .eq(InsProduct::getState, 1)
-                .eq(InsProduct::getInsResult, 0));
-
-        // 杩囨护鍒ゆ柇鏄湁澶嶆祴鐨勫�兼槸鍚堟牸鐨�
-        List<InsProduct> productList = insProducts.stream().filter(insProduct -> {
-            // 鏌ヨ涓嶅悎鏍煎娴�
-            Long count = insUnqualifiedRetestProductMapper.selectCount(Wrappers.<InsUnqualifiedRetestProduct>lambdaQuery()
-                    .eq(InsUnqualifiedRetestProduct::getInsProductId, insProduct.getId())
-                    .ne(InsUnqualifiedRetestProduct::getInsResult, 0));
-            if (count != 2) {
-                return true;
+        if (isDirectTemplateOrder(orderId)) {
+            List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateService.list(
+                    Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                            .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
+                            .orderByAsc(InsOrderStandardTemplate::getSort,
+                                    InsOrderStandardTemplate::getId));
+            for (InsOrderStandardTemplate snapshot : snapshots) {
+                String templateName = StringUtils.defaultIfBlank(snapshot.getName(),
+                        "妯℃澘ID " + snapshot.getTemplateId());
+                if (snapshot.getDetectionTime() == null) {
+                    collect.add("璇烽�夋嫨鈥�" + templateName + "鈥濈殑妫�娴嬫椂闂�");
+                }
+                if (StringUtils.isBlank(snapshot.getDetectionPlace())) {
+                    collect.add("璇峰~鍐欌��" + templateName + "鈥濈殑妫�娴嬪湴鐐�");
+                }
             }
-            return false;
-        }).collect(Collectors.toList());
-        if (productList.size() > 0) {
-            collect = productList.stream().map(insProduct -> {
-                return insProduct.getInspectionItem() + "-" + insProduct.getInspectionItemSubclass();
-            }).collect(Collectors.toList());
+            map.put("errorMsg", collect);
+            map.put("missingExportItems", getMissingTemplateExportValues(orderId));
+            map.put("unInsOrderCount", 0L);
+            return map;
         }
-        //鏌ヨifs鎷嗗垎璁㈠崟鏄惁鏈夊凡涓嬪崟浣嗘槸鏈瀹岀殑鍗曞瓙
-        long count = 0L;
-        InsOrder insOrder = insOrderMapper.selectById(orderId);
-        if(Objects.nonNull(insOrder.getIfsInventoryId())){
-            IfsInventoryQuantity one = ifsInventoryQuantityMapper.selectById(insOrder.getIfsInventoryId());
-            //杩囨护鍑轰笉鍚堟牸鎴栨湭鎻愪氦鐨勫崟瀛�
-            count = ifsInventoryQuantityMapper.selectSplitOrderList(one.getPartNo(),one.getLineNo(),one.getReleaseNo(),one.getReceiptNo(),one.getOrderNo())
-                    .stream()
-                    .filter(f->(Objects.nonNull(f.getInsOrderId()) && !Objects.equals(f.getInsOrderId(),orderId)) && (Objects.isNull(f.getInsResult()) || 0==f.getInsResult())).count();
-        }
-        map.put("errorMsg",collect);
-        map.put("unInsOrderCount",count);
+        map.put("errorMsg", collect);
+        map.put("unInsOrderCount", 0L);
         return map;
     }
 
@@ -551,6 +604,275 @@
         });
     }
 
+    /**
+     * 鏂拌仛璁㈠崟鐨勫灞� key 鏄� templateId锛屼笉鏄� ins_product.id锛涘洜姝ゅ崟鐙寜妯℃澘蹇収淇濆瓨銆�
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveTemplateContext(SaveTemplateContextDto dto) {
+        if (dto.getOrderId() == null || dto.getSampleId() == null || dto.getTemplateId() == null) {
+            throw new ErrorException("璁㈠崟銆佹牱鍝佸拰妯℃澘涓嶈兘涓虹┖");
+        }
+        InsSample sample = insSampleMapper.selectById(dto.getSampleId());
+        if (sample == null || !Objects.equals(sample.getInsOrderId(), dto.getOrderId())) {
+            throw new ErrorException("鏍峰搧涓嶅睘浜庡綋鍓嶆楠屽崟");
+        }
+        InsOrderStandardTemplate snapshot = insOrderStandardTemplateService.getOne(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, dto.getOrderId())
+                        .eq(InsOrderStandardTemplate::getTemplateId, dto.getTemplateId())
+                        .last("limit 1"));
+        if (snapshot == null) {
+            throw new ErrorException("鏈壘鍒板綋鍓嶈鍗曠殑鍘熷璁板綍妯℃澘蹇収");
+        }
+        ensureDirectTemplateOrderState(dto.getOrderId());
+        List<InsProduct> products = insProductMapper.selectList(Wrappers.<InsProduct>lambdaQuery()
+                .eq(InsProduct::getInsSampleId, dto.getSampleId())
+                .eq(InsProduct::getTemplateId, dto.getTemplateId())
+                .isNotNull(InsProduct::getTemplateRowIndex)
+                .eq(InsProduct::getState, 1));
+
+        JSONObject values = JSON.parseObject(JSON.toJSONString(dto.getValues() == null
+                ? Collections.emptyMap() : dto.getValues()));
+        // 妫�楠屾湡闂撮〉闈㈠睍绀虹殑鏄爣鍑嗙淮鎶や腑鐨勬渶鏂版ā鏉匡紝淇濆瓨鏍¢獙蹇呴』浣跨敤鍚屼竴浠芥ā鏉匡紝
+        // 閬垮厤妯℃澘鏂板鈥滃鍑哄�尖�濇壒娉ㄥ悗浠嶆寜涓嬪崟鏃剁殑鏃у揩鐓ц鎶ユ湭閰嶇疆銆�
+        String currentTemplateThing = getCurrentTemplateThing(snapshot);
+        List<String> exportValueCoordinates = getExportValueCoordinates(currentTemplateThing).values().stream()
+                .flatMap(Collection::stream)
+                .distinct()
+                .collect(Collectors.toList());
+        if (exportValueCoordinates.isEmpty()) {
+            throw new ErrorException("妯℃澘蹇呴』鑷冲皯閰嶇疆涓�涓鍑哄�兼壒娉�");
+        }
+
+        // 瀵煎嚭鍊兼寜妯℃澘閰嶇疆锛屼笉鍐嶈姹傛瘡涓楠岄」鍖哄潡鍒嗗埆閰嶇疆銆備竴涓ā鏉垮彇绗竴涓鍑哄�间綔涓虹粨鏋溿��
+        String exportValueCoordinate = exportValueCoordinates.get(0);
+        JSONObject exportValueCell = values.getJSONObject(exportValueCoordinate);
+        String exportValue = exportValueCell == null || exportValueCell.get("v") == null
+                ? "" : String.valueOf(exportValueCell.get("v"));
+        Integer userId = SecurityUtils.getUserId().intValue();
+        for (InsProduct product : products) {
+            InsProduct update = new InsProduct();
+            update.setId(product.getId());
+            update.setLastValue(exportValue);
+            update.setUpdateUser(userId);
+            insProductMapper.updateById(update);
+            insProductUserMapper.insert(new InsProductUser(null, userId, LocalDateTime.now(), product.getId()));
+        }
+        snapshot.setRecordValues(values.toJSONString());
+        insOrderStandardTemplateService.updateById(snapshot);
+
+        if (hasSavedTemplateValue(values)) {
+            markInspectionStarted(dto.getOrderId(), products);
+        }
+
+        Long unfinished = insProductMapper.selectCount(Wrappers.<InsProduct>lambdaQuery()
+                .eq(InsProduct::getInsSampleId, dto.getSampleId())
+                .isNotNull(InsProduct::getTemplateRowIndex)
+                .eq(InsProduct::getState, 1)
+                .and(w -> w.isNull(InsProduct::getLastValue).or().eq(InsProduct::getLastValue, "")));
+        sample.setInsState(unfinished == 0 ? 2 : 1);
+        insSampleMapper.updateById(sample);
+    }
+
+    /**
+     * 绗竴娆℃垚鍔熶繚瀛樹换鎰忛潪绌烘ā鏉垮�兼椂锛岃褰曠湡瀹炴楠屽紑濮嬫椂闂村苟灏嗕换鍔′粠寰呮鏀逛负鍦ㄦ銆�
+     */
+    private void markInspectionStarted(Integer orderId, List<InsProduct> products) {
+        LocalDateTime now = LocalDateTime.now();
+        insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
+                .eq(InsOrder::getId, orderId)
+                .isNull(InsOrder::getFirstInspectDate)
+                .set(InsOrder::getFirstInspectDate, now)
+                // 淇濈暀鍘熷瓧娈典緵妫�楠屼换鍔″垪琛ㄤ腑鐨勨�滄楠屽紑濮嬫椂闂粹�濈户缁睍绀恒��
+                .set(InsOrder::getInsTime, now));
+
+        Set<String> laboratories = products.stream()
+                .map(InsProduct::getSonLaboratory)
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.toSet());
+        if (laboratories.isEmpty() && isDirectTemplateOrder(orderId)) {
+            laboratories.add(DIRECT_TEMPLATE_LABORATORY);
+        }
+        if (!laboratories.isEmpty()) {
+            insOrderStateMapper.update(null, Wrappers.<InsOrderState>lambdaUpdate()
+                    .eq(InsOrderState::getInsOrderId, orderId)
+                    .in(InsOrderState::getLaboratory, laboratories)
+                    .eq(InsOrderState::getInsState, 0)
+                    .set(InsOrderState::getInsTime, now)
+                    .set(InsOrderState::getInsState, 1));
+        }
+    }
+
+    private boolean hasSavedTemplateValue(JSONObject values) {
+        if (values == null || values.isEmpty()) {
+            return false;
+        }
+        for (Object rawCell : values.values()) {
+            JSONObject cell = rawCell instanceof JSONObject
+                    ? (JSONObject) rawCell
+                    : JSON.parseObject(JSON.toJSONString(rawCell));
+            if (cell != null && hasMeaningfulValue(cell.get("v"))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean hasMeaningfulValue(Object value) {
+        if (value == null) {
+            return false;
+        }
+        if (value instanceof CharSequence) {
+            return StringUtils.isNotBlank(value.toString());
+        }
+        if (value instanceof Collection) {
+            return ((Collection<?>) value).stream().anyMatch(this::hasMeaningfulValue);
+        }
+        if (value instanceof Map) {
+            return ((Map<?, ?>) value).values().stream().anyMatch(this::hasMeaningfulValue);
+        }
+        return true;
+    }
+
+    private NavigableMap<Integer, List<String>> getExportValueCoordinates(String compressedThing) {
+        if (StrUtil.isBlank(compressedThing)) {
+            throw new ErrorException("鍘熷璁板綍妯℃澘涓虹┖");
+        }
+        String thing;
+        try {
+            thing = GZipUtil.uncompress(compressedThing);
+        } catch (Exception ignored) {
+            thing = compressedThing;
+        }
+        JSONArray cellData = JSON.parseObject(thing).getJSONArray("data").getJSONObject(0).getJSONArray("celldata");
+        NavigableMap<Integer, List<String>> result = new TreeMap<>();
+        Integer inspectionResultColumn = null;
+        for (Object item : cellData) {
+            JSONObject cell = JSON.parseObject(JSON.toJSONString(item));
+            Integer column = cell.getInteger("c");
+            JSONObject value = cell.getJSONObject("v");
+            String displayValue = value == null ? null : value.getString("v");
+            if (inspectionResultColumn == null && "妫�楠岀粨鏋�".equals(StringUtils.trim(displayValue))) {
+                inspectionResultColumn = column;
+            }
+        }
+        for (Object item : cellData) {
+            JSONObject cell = JSON.parseObject(JSON.toJSONString(item));
+            JSONObject value = cell.getJSONObject("v");
+            JSONObject ps = value == null ? null : value.getJSONObject("ps");
+            if (ps == null || !"瀵煎嚭鍊�".equals(ps.getString("value"))) {
+                continue;
+            }
+            Integer row = cell.getInteger("r");
+            Integer column = cell.getInteger("c");
+            if (row != null && column != null) {
+                // 鈥滃鍑哄�尖�濇爣鍦ㄦ娴嬮」鐩爣棰樺崟鍏冩牸鏃讹紝鐪熸瀵煎嚭鐨勫�兼槸鍚屼竴琛屸�滄楠岀粨鏋溾�濆垪鐨勫崟鍏冩牸銆�
+                String coordinate = row + "-" + column;
+                String resultCoordinate = row + "-" + inspectionResultColumn;
+                if (inspectionResultColumn != null && !Objects.equals(column, inspectionResultColumn)) {
+                    coordinate = resultCoordinate;
+                }
+                result.computeIfAbsent(row, ignored -> new ArrayList<>()).add(coordinate);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 妫�楠屼繚瀛樸�佹彁浜ゆ牎楠岃鍙栨爣鍑嗙淮鎶や腑鐨勫疄鏃舵ā鏉匡紱妯℃澘宸茶鍒犻櫎鎴栧唴瀹逛负绌烘椂鎵嶅洖閫�璁㈠崟蹇収銆�
+     * 妫�楠屽畬鎴愬悗鐨勫睍绀哄拰鎶ュ憡浠嶇敱璁㈠崟蹇収璐熻矗锛屼笉浼氶殢鏍囧噯妯℃澘鍚庣画淇敼鑰屽彉鍖栥��
+     */
+    private String getCurrentTemplateThing(InsOrderStandardTemplate snapshot) {
+        if (snapshot != null && snapshot.getTemplateId() != null) {
+            StandardTemplate currentTemplate = standardTemplateService.getById(snapshot.getTemplateId());
+            if (currentTemplate != null && StrUtil.isNotBlank(currentTemplate.getThing())) {
+                return currentTemplate.getThing();
+            }
+        }
+        return snapshot == null ? null : snapshot.getThing();
+    }
+
+    /**
+     * 鐩寸粦妯℃澘璁㈠崟鍙牎楠屾ā鏉夸腑鏍囪涓衡�滃鍑哄�尖�濈殑鍗曞厓鏍硷紝涓嶄娇鐢ㄦ楠岄」鐘舵�佹垨 last_value 鍒ゅ畾銆�
+     */
+    private List<String> getMissingTemplateExportValues(Integer orderId) {
+        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateService.list(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
+                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
+        List<String> missing = new ArrayList<>();
+        if (snapshots.isEmpty()) {
+            missing.add("褰撳墠璁㈠崟鏈壘鍒板師濮嬭褰曟ā鏉垮揩鐓�");
+            return missing;
+        }
+        for (InsOrderStandardTemplate snapshot : snapshots) {
+            Integer templateId = snapshot.getTemplateId();
+            List<String> coordinates = getExportValueCoordinates(getCurrentTemplateThing(snapshot)).values().stream()
+                    .flatMap(Collection::stream)
+                    .distinct()
+                    .collect(Collectors.toList());
+            String templateName = StringUtils.defaultIfBlank(snapshot.getName(), "妯℃澘ID " + templateId);
+            if (coordinates.isEmpty()) {
+                missing.add(templateName + "锛堟湭閰嶇疆瀵煎嚭鍊硷級");
+                continue;
+            }
+
+            JSONObject recordValues = StrUtil.isBlank(snapshot.getRecordValues())
+                    ? new JSONObject() : JSON.parseObject(snapshot.getRecordValues());
+            // 淇濆瓨鏃跺悓鏍峰彧鍙栨ā鏉夸腑鐨勭涓�涓鍑哄�硷紝鎻愪氦鏍¢獙涓庝繚瀛樺彛寰勪繚鎸佷竴鑷淬��
+            String coordinate = coordinates.get(0);
+            JSONObject cell = recordValues.getJSONObject(coordinate);
+            Object value = cell == null ? null : cell.get("v");
+            if (value == null || StringUtils.isBlank(String.valueOf(value))) {
+                missing.add(templateName + "锛�" + coordinate + "锛�");
+            }
+        }
+        return missing;
+    }
+
+    @Override
+    public List<Integer> getOrderDeviceIds(Integer orderId) {
+        return insOrderDeviceRecordMapper.selectList(Wrappers.<InsOrderDeviceRecord>lambdaQuery()
+                        .eq(InsOrderDeviceRecord::getInsOrderId, orderId))
+                .stream().map(InsOrderDeviceRecord::getDeviceId).filter(Objects::nonNull).distinct()
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveOrderDevices(SaveOrderDevicesDto dto) {
+        if (dto.getOrderId() == null || insOrderMapper.selectById(dto.getOrderId()) == null) {
+            throw new ErrorException("妫�楠屽崟涓嶅瓨鍦�");
+        }
+        Set<Integer> deviceIds = dto.getDeviceIds() == null ? Collections.emptySet() : dto.getDeviceIds().stream()
+                .filter(Objects::nonNull).collect(Collectors.toSet());
+        List<InsOrderDeviceRecord> oldRecords = insOrderDeviceRecordMapper.selectList(
+                Wrappers.<InsOrderDeviceRecord>lambdaQuery().eq(InsOrderDeviceRecord::getInsOrderId, dto.getOrderId()));
+        Set<Integer> oldIds = oldRecords.stream().map(InsOrderDeviceRecord::getDeviceId).collect(Collectors.toSet());
+        if (!oldIds.isEmpty()) {
+            insOrderDeviceRecordMapper.delete(Wrappers.<InsOrderDeviceRecord>lambdaQuery()
+                    .eq(InsOrderDeviceRecord::getInsOrderId, dto.getOrderId())
+                    .notIn(!deviceIds.isEmpty(), InsOrderDeviceRecord::getDeviceId, deviceIds));
+        }
+        InsOrder order = insOrderMapper.selectById(dto.getOrderId());
+        User user = userMapper.selectById(SecurityUtils.getUserId().intValue());
+        List<InsOrderDeviceRecord> additions = deviceIds.stream().filter(id -> !oldIds.contains(id)).map(id -> {
+            InsOrderDeviceRecord record = new InsOrderDeviceRecord();
+            record.setInsOrderId(dto.getOrderId());
+            record.setDeviceId(id);
+            record.setSampleCode(order.getEntrustCode());
+            record.setUseBefore(1);
+            record.setUseAfter(1);
+            record.setUsePersonId(user.getId());
+            record.setUsePerson(user.getName());
+            return record;
+        }).collect(Collectors.toList());
+        if (!additions.isEmpty()) {
+            insOrderDeviceRecordService.saveBatch(additions);
+        }
+    }
+
     private synchronized void addDeviceRecord(InsSample insSample, Integer userId) {
         InsOrder order = insOrderMapper.selectById(insSample.getInsOrderId());
         User user = userMapper.selectById(userId);
@@ -624,39 +946,55 @@
      * @param order
      * @param insProducts
      */
-    private void getTemplateThing(InsOrder order, List<InsProduct> insProducts) {
+    private void getTemplateThing(InsOrder order, List<InsProduct> insProducts, String laboratory) {
         Set<Integer> set = new HashSet<>();
-        // 鏌ヨ璁㈠崟鐘舵�佸垽鏂槸鍚︽槸鏌ュ巻鍙叉ā鏉�
-        if (order.getIsFirstSubmit() != null && order.getIsFirstSubmit().equals(1)) {
-            InsOrderState insOrderState = insOrderStateMapper.selectOne(Wrappers.<InsOrderState>lambdaQuery()
-                    .eq(InsOrderState::getInsOrderId, order.getId())
-                    .last("limit 1"));
-            if (insOrderState != null && (!insOrderState.getInsState().equals(3) || !insOrderState.getInsState().equals(5))) {
-                for (InsProduct product : insProducts) {
-                    if (product.getTemplateId() == null) {
-                        product.setTemplate(new ArrayList<>());
+        Map<Integer, String> recordValuesByTemplate = new HashMap<>();
+        for (InsOrderStandardTemplate snapshot : insOrderStandardTemplateService.list(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, order.getId()))) {
+            // record_values 鍦ㄩ娆″~鍐欏墠鍏佽涓虹┖锛汣ollectors.toMap 涓嶆帴鍙� null value銆�
+            if (snapshot.getTemplateId() != null && snapshot.getRecordValues() != null) {
+                recordValuesByTemplate.putIfAbsent(snapshot.getTemplateId(), snapshot.getRecordValues());
+            }
+        }
+        boolean directTemplateOrder = insProducts.stream()
+                .anyMatch(product -> product.getTemplateRowIndex() != null);
+        Long snapshotCount = insOrderStandardTemplateService.count(Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                .eq(InsOrderStandardTemplate::getInsOrderId, order.getId()));
+        InsOrderState insOrderState = insOrderStateMapper.selectOne(Wrappers.<InsOrderState>lambdaQuery()
+                .eq(InsOrderState::getInsOrderId, order.getId())
+                .eq(StringUtils.isNotBlank(laboratory), InsOrderState::getLaboratory, laboratory)
+                .last("limit 1"));
+        Integer insState = insOrderState == null ? null : insOrderState.getInsState();
+        boolean hasSnapshot = snapshotCount > 0 && (directTemplateOrder
+                || Objects.equals(order.getIsFirstSubmit(), 1));
+        // 寰呮楠屻�佹楠屼腑浣跨敤鍩虹閰嶇疆鐨勬渶鏂版ā鏉匡紱宸叉楠屽強鍚庣画鐘舵�佷娇鐢ㄨ鍗曞揩鐓с��
+        boolean useSnapshot = hasSnapshot && insState != null && insState >= 2;
+        if (useSnapshot) {
+            for (InsProduct product : insProducts) {
+                if (product.getTemplateId() == null) {
+                    product.setTemplate(new ArrayList<>());
+                    continue;
+                }
+                if (set.add(product.getTemplateId())) {
+                    InsOrderStandardTemplate one = insOrderStandardTemplateService.getOne(Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                            .eq(InsOrderStandardTemplate::getTemplateId, product.getTemplateId())
+                            .eq(InsOrderStandardTemplate::getInsOrderId, order.getId()));
+                    if (one == null || StrUtil.isEmpty(one.getThing())) {
+                        // 蹇収涓嶅畬鏁存椂鍏佽鍚庨潰鐨勬渶鏂版ā鏉挎煡璇㈠厹搴曘��
+                        set.remove(product.getTemplateId());
                         continue;
                     }
-                    String thing = null;
-                    if (product.getTemplateId() != null && set.add(product.getTemplateId())) {
-                        // 鏌ヨ鍘嗗彶妯℃澘
-                        InsOrderStandardTemplate one = insOrderStandardTemplateService.getOne(Wrappers.<InsOrderStandardTemplate>lambdaQuery()
-                                .eq(InsOrderStandardTemplate::getTemplateId, product.getTemplateId())
-                                .eq(InsOrderStandardTemplate::getInsOrderId, order.getId()));
-                        thing = one.getThing();
-                        if (StrUtil.isNotEmpty(thing)) {
-                            thing = GZipUtil.uncompress(thing);
-                            JSONObject sheet = JSON.parseObject(thing).getJSONArray("data").getJSONObject(0);
-                            JSONObject config = sheet.getJSONObject("config");
-                            List<JSONObject> cellData = JSON.parseArray(JSON.toJSONString(sheet.get("celldata")), JSONObject.class);
-                            Map<String, Object> style = new HashMap<>();
-                            style.put("rowlen", config.get("rowlen"));
-                            style.put("columnlen", config.get("columnlen"));
-                            product.setTemplate(cellData);
-                            product.setStyle(style);
-                            product.setTemplateName(one.getName());
-                        }
-                    }
+                    String thing = GZipUtil.uncompress(one.getThing());
+                    JSONObject sheet = JSON.parseObject(thing).getJSONArray("data").getJSONObject(0);
+                    JSONObject config = sheet.getJSONObject("config");
+                    List<JSONObject> cellData = JSON.parseArray(JSON.toJSONString(sheet.get("celldata")), JSONObject.class);
+                    Map<String, Object> style = new HashMap<>();
+                    style.put("rowlen", config.get("rowlen"));
+                    style.put("columnlen", config.get("columnlen"));
+                    product.setTemplate(cellData);
+                    product.setStyle(style);
+                    product.setTemplateName(one.getName());
                 }
             }
         }
@@ -680,6 +1018,7 @@
                 product.setStyle(style);
                 product.setTemplateName(standardTemplateService.getStandTempNameById(product.getTemplateId()));
             }
+            product.setRecordValues(recordValuesByTemplate.get(product.getTemplateId()));
         }
     }
 
@@ -769,11 +1108,14 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public int submitPlan(Integer orderId, String laboratory, Integer verifyUser, String entrustCode, Boolean registerInsResults, Integer reportType) {
-        if (!Objects.equals(DETECTION_REPORT, reportType) && !Objects.equals(INSPECTION_REPORT, reportType)) {
-            throw new ErrorException("璇烽�夋嫨鎶ュ憡绫诲瀷");
-        }
-        InsOrder order = insOrderMapper.selectById(orderId);
+    public int submitPlan(Integer orderId, String laboratory, Integer verifyUser, String entrustCode, Boolean registerInsResults) {
+        ensureDirectTemplateOrderState(orderId);
+        int reportType = DETECTION_REPORT;
+        InsOrder order = insOrderMapper.selectOne(Wrappers.<InsOrder>lambdaQuery()
+                .eq(InsOrder::getId, orderId)
+                .last("FOR UPDATE"));
+        boolean isInspectionReport = Objects.equals(INSPECTION_REPORT, reportType);
+        registerInsResults = isInspectionReport && Boolean.TRUE.equals(registerInsResults);
         // 1. 鍒ゆ柇鏄惁鏈夐噸澶嶇紪鍙�, 鏈夐噸澶嶇紪鍙峰仛鎻愰啋
         Long codeCount = insOrderMapper.selectCount(Wrappers.<InsOrder>lambdaQuery()
                 .ne(InsOrder::getState, -1)
@@ -784,31 +1126,43 @@
         }
 
         // 2. 鍒ゆ柇璇ヨ鍗曟槸鍚︽槸绗竴娆$敓浜�(鍚庣画鎶ュ憡鐢熸垚鍙彇绗竴娆℃彁浜ゆ椂闂�)
-        if (!(order.getIsFirstSubmit() != null && order.getIsFirstSubmit().equals(1))) {
+        boolean firstSubmit = !Objects.equals(order.getIsFirstSubmit(), 1);
+        if (firstSubmit) {
             insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                     .eq(InsOrder::getId, orderId)
                     .set(InsOrder::getIsFirstSubmit, 1)
                     .set(InsOrder::getFirstSubmitDate, LocalDateTime.now()));
         }
 
-        // 3. 鍒ゆ柇鏄惁鏈夋湭妫�椤�
+        // 3. 鐩寸粦妯℃澘浠呮牎楠屸�滃鍑哄�尖�濓紱鏅�氳鍗曟部鐢ㄥ師妫�楠岄」鏍¢獙銆�
         List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery()
                 .eq(InsSample::getInsOrderId, orderId).select(InsSample::getId));
         List<Integer> InsSampleIds = insSamples.stream().map(InsSample::getId).collect(Collectors.toList());
-        List<InsProduct> insProducts = insProductMapper.selectList(Wrappers.<InsProduct>lambdaQuery()
-                .in(InsProduct::getInsSampleId, InsSampleIds)
-                .eq(InsProduct::getSonLaboratory, laboratory)
-                .eq(InsProduct::getState, 1)
-                .and(wrapper -> {
-                    if (Objects.equals(reportType, DETECTION_REPORT)) {
-                        wrapper.isNull(InsProduct::getLastValue).or().eq(InsProduct::getLastValue, "");
-                    } else {
-                        wrapper.isNull(InsProduct::getInsResult).or().eq(InsProduct::getInsResult, 2);
-                    }
-                })
-                .ne(InsProduct::getIsBinding, 1));
-        if (Objects.equals(reportType, INSPECTION_REPORT)) {
-            insProducts.addAll(insProductMapper.selectFiberInsProduct(InsSampleIds, laboratory));
+        boolean directTemplateOrder = isDirectTemplateOrder(orderId);
+        List<InsProduct> insProducts;
+        if (directTemplateOrder) {
+            List<String> missingExportValues = getMissingTemplateExportValues(orderId);
+            if (!missingExportValues.isEmpty()) {
+                throw new ErrorException("妯℃澘涓殑瀵煎嚭鍊间笉鑳戒负绌猴細\n" + String.join("\n", missingExportValues));
+            }
+            insProducts = Collections.emptyList();
+        } else {
+            insProducts = insProductMapper.selectList(Wrappers.<InsProduct>lambdaQuery()
+                    .in(InsProduct::getInsSampleId, InsSampleIds)
+                    .eq(InsProduct::getSonLaboratory, laboratory)
+                    .eq(InsProduct::getState, 1)
+                    .and(wrapper -> {
+                        if (Objects.equals(reportType, DETECTION_REPORT)) {
+                            wrapper.isNull(InsProduct::getLastValue).or().eq(InsProduct::getLastValue, "");
+                        } else {
+                            wrapper.isNull(InsProduct::getInsResult).or().eq(InsProduct::getInsResult, 2);
+                        }
+                    })
+                    .ne(InsProduct::getIsBinding, 1));
+        }
+        if (!directTemplateOrder && Objects.equals(reportType, INSPECTION_REPORT)) {
+            List<InsProduct> insProducts1 = insProductMapper.selectFiberInsProduct(InsSampleIds, laboratory);
+            insProducts.addAll(insProducts1);
         }
         if (insProducts.size() > 0) {
             String str = "";
@@ -872,28 +1226,24 @@
             }
         });
 
-        // 8.鎻愪氦鐢熸垚鎶ュ憡
-        this.generateReport(orderId, reportType);
+        // 8.棣栨鎻愪氦鏃跺皢鏈妫�楠屼娇鐢ㄧ殑鏈�鏂版ā鏉垮喕缁撲负璁㈠崟蹇収銆�
+        Long snapshotCount = insOrderStandardTemplateService.count(Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                .eq(InsOrderStandardTemplate::getInsOrderId, orderId));
+        if (firstSubmit || snapshotCount == 0) {
+            this.templateCopy(orderId, InsSampleIds);
+        }
 
-        // 9.娣诲姞涓存椂pdf鐢熸垚鍦板潃
-        InsReport report = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
-                .eq(InsReport::getInsOrderId, orderId));
-        String tempUrlPdf = this.wordToPdfTemp(report.getUrl().replace("/word", wordUrl));
-        report.setTempUrlPdf("/word/" + tempUrlPdf);
-        insReportMapper.updateById(report);
+        // 9. 妫�娴嬫姤鍛婁粎鍦ㄥ叏閮ㄥ鏍搁�氳繃鏃剁敓鎴愶紝鎻愪氦澶嶆牳闃舵涓嶇敓鎴愭姤鍛娿��
 
-        // 10.鍘熷璁板綍妯℃澘澶嶅埗(娣诲姞澶囦唤, 閬垮厤淇敼鍘熷妯℃澘褰卞搷鍒板凡缁忓畬鎴愮殑鍗曞瓙)
-        this.templateCopy(orderId, InsSampleIds);
-
-        // 11.鎴愬搧鎶芥牱娣诲姞鍚堟牸鐘舵��
+        // 10.鎴愬搧鎶芥牱娣诲姞鍚堟牸鐘舵��
         // 鍒ゆ柇鏄惁鏈夋娊鏍蜂俊鎭�
-        if (order.getQuarterItemId() != null) {
+        if (isInspectionReport && order.getQuarterItemId() != null) {
             // 鍒ゆ柇鏄惁鏈変笉鍚堟牸
             this.addProductSpotCheck(insSamples, order);
         }
 
 
-        // 12.娣诲姞璁㈠崟璐圭敤缁熻淇℃伅
+        // 11.娣诲姞璁㈠崟璐圭敤缁熻淇℃伅
         List<InsProduct> productList = insProductMapper.selectProductByOrderId(orderId);
         // 鍒犻櫎鍘熸湰璐圭敤淇℃伅
         insOrderRatesService.remove(Wrappers.<InsOrderRates>lambdaQuery()
@@ -916,7 +1266,7 @@
         insOrderRatesService.saveBatch(orderRatesList);
 
 
-        // 13.鍙戦�佷紒涓氬井淇¢�氱煡
+        // 12.鍙戦�佷紒涓氬井淇¢�氱煡
         // 鏌ヨ鍘熸潗鏂�
         IfsInventoryQuantity ifsInventoryQuantity = ifsInventoryQuantityMapper.selectById(order.getIfsInventoryId());
         // 鏌ヨ鏍峰搧淇℃伅
@@ -935,20 +1285,20 @@
             }
             //鍙戦�佷紒涓氬井淇℃秷鎭�氱煡  鎻愪氦澶嶆牳
             try {
-                WxCpUtils.inform(sendUserAccount, message, null);
+//                WxCpUtils.inform(sendUserAccount, message, null);
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
         });
 
-        // 14.ifs绉诲簱(鍘熸潗鏂欓渶瑕佽繘琛岀Щ搴撴搷浣�) --> 鏈�鍚庢墽琛�,鍥犱负澶辫触鏃犳硶鍥炴粴
-        if (ifsInventoryQuantity != null) {
+        // 13.ifs绉诲簱(鍘熸潗鏂欓渶瑕佽繘琛岀Щ搴撴搷浣�) --> 鏈�鍚庢墽琛�,鍥犱负澶辫触鏃犳硶鍥炴粴
+        if (ifsInventoryQuantity != null && isInspectionReport) {
             // 鐧昏妫�楠岀粨鏋�
             // 鍒ゆ柇鏄惁鏈変笉鍚堟牸, 鏈変笉鍚堟牸涓嶈兘绉诲簱
             // todo: ifs绉诲簱
             insReportService.isRawMaterial(order,registerInsResults,false);
 
-            // 15 鍒ゆ柇褰撳墠鏍峰搧鏄惁涓哄師鏉愭枡, 鍘熸潗鏂欓渶瑕佽繘琛屾暟鎹垎鏋�, 鍒ゆ柇涔嬪墠10鏉℃暟鎹悓涓�涓緵搴斿晢, 鍚屼竴涓楠岄」鐨勫亸宸槸鍚﹁秴杩�10%
+            // 14 鍒ゆ柇褰撳墠鏍峰搧鏄惁涓哄師鏉愭枡, 鍘熸潗鏂欓渶瑕佽繘琛屾暟鎹垎鏋�, 鍒ゆ柇涔嬪墠10鏉℃暟鎹悓涓�涓緵搴斿晢, 鍚屼竴涓楠岄」鐨勫亸宸槸鍚﹁秴杩�10%
             // 鏌ヨifs淇℃伅鑾峰彇鑾峰彇鍓�10涓緵搴斿晢涓�鏍风殑, 妫�楠岄」涓�鏍蜂俊鎭�
             threadPoolTaskExecutor.execute(() -> {
                 // 娣诲姞鍒嗘瀽鏁版嵁
@@ -956,7 +1306,7 @@
             });
 
 
-        } else {
+        } else if (isInspectionReport && !directTemplateOrder) {
             // 淇敼鎴愬搧鐘舵��
             // 鍒ゆ柇鏄惁鏈変笉鍚堟牸
             Long unqualifiedCount = insReportService.getUnqualifiedCount(order);
@@ -983,7 +1333,7 @@
     private void addAnalysis(List<InsProduct> productList, IfsInventoryQuantity ifsInventoryQuantity, InsOrder order, String sendUserAccount) {
         for (InsProduct insProduct : productList) {
             // 鍒ゆ柇鏄惁鏄暟鍊肩被鍨�
-            if (insProduct.getInspectionValueType().equals("1") && insProduct.getInsResult().equals(1)) {
+            if ("1".equals(insProduct.getInspectionValueType()) && Objects.equals(insProduct.getInsResult(), 1)) {
                 List<InsProductDeviationWarningDetail> insProductAnalysisDtoList = insProductMapper.selectAnalysis(insProduct, ifsInventoryQuantity.getSupplierName());
 
                 if (CollectionUtils.isEmpty(insProductAnalysisDtoList)) {
@@ -1113,29 +1463,78 @@
      * @param ids
      */
     private void templateCopy(Integer orderId, List<Integer> ids) {
-        // 鍒犻櫎鍘熸湰妯℃澘
-        insOrderStandardTemplateService.remove(Wrappers.<InsOrderStandardTemplate>lambdaQuery()
-                .eq(InsOrderStandardTemplate::getInsOrderId, orderId));
-        // 澶嶅埗妯℃澘
-        Set<Integer> set = new HashSet<>();
+        // 淇濈暀妯℃澘椤电椤哄簭锛屽苟鍦ㄩ娆℃彁浜ゅ鏍告椂鐢ㄦ楠屾湡闂撮噰鐢ㄧ殑鏈�鏂版ā鏉垮埛鏂板揩鐓с��
+        // 鐩寸粦妯℃澘璁㈠崟蹇呴』浠ュ師璁㈠崟蹇収涓烘ā鏉挎竻鍗曪紝涓嶈兘鍙寜 ins_product 閲嶅缓锛�
+        // 娌℃湁鈥滄楠岄」鈥濇爣璁扮殑妯℃澘涓嶄細鐢熸垚 ins_product锛屽惁鍒欐彁浜ゅ悗浼氳閿欒鍒犻櫎銆�
+        List<InsOrderStandardTemplate> oldSnapshots = insOrderStandardTemplateService.list(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
+                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
+        Map<Integer, Integer> templateSortMap = new HashMap<>();
+        Map<Integer, String> templateRecordValuesMap = new HashMap<>();
+        Map<Integer, String> templateTemperatureMap = new HashMap<>();
+        Map<Integer, String> templateHumidityMap = new HashMap<>();
+        Map<Integer, InsOrderStandardTemplate> oldSnapshotMap = new LinkedHashMap<>();
+        int nextTemplateSort = 0;
+        for (InsOrderStandardTemplate snapshot : oldSnapshots) {
+            if (snapshot.getTemplateId() != null) {
+                oldSnapshotMap.putIfAbsent(snapshot.getTemplateId(), snapshot);
+                if (snapshot.getSort() != null) {
+                    templateSortMap.putIfAbsent(snapshot.getTemplateId(), snapshot.getSort());
+                    nextTemplateSort = Math.max(nextTemplateSort, snapshot.getSort() + 1);
+                }
+                if (snapshot.getRecordValues() != null) {
+                    templateRecordValuesMap.putIfAbsent(snapshot.getTemplateId(), snapshot.getRecordValues());
+                }
+                if (snapshot.getTemperature() != null) {
+                    templateTemperatureMap.putIfAbsent(snapshot.getTemplateId(), snapshot.getTemperature());
+                }
+                if (snapshot.getHumidity() != null) {
+                    templateHumidityMap.putIfAbsent(snapshot.getTemplateId(), snapshot.getHumidity());
+                }
+            }
+        }
+
+        LinkedHashSet<Integer> templateIds = new LinkedHashSet<>();
+        if (isDirectTemplateOrder(orderId)) {
+            // 鍏堝姞鍏ヨ鍗曞師鏉ョ粦瀹氱殑鍏ㄩ儴妯℃澘锛屽寘鎷笉鐢熸垚妫�楠岄」璁板綍鐨勬ā鏉裤��
+            templateIds.addAll(oldSnapshotMap.keySet());
+        }
         List<InsProduct> insProductList = insProductMapper.selectList(Wrappers.<InsProduct>lambdaQuery()
                 .in(InsProduct::getInsSampleId, ids)
                 .select(InsProduct::getTemplateId));
+        insProductList.stream()
+                .map(InsProduct::getTemplateId)
+                .filter(Objects::nonNull)
+                .forEach(templateIds::add);
 
-        for (InsProduct product : insProductList) {
-            // 鏌ヨ妯℃澘id
-            if (product.getTemplateId() != null && set.add(product.getTemplateId())) {
-                StandardTemplate standardTemplate = standardTemplateService.getById(product.getTemplateId());
-                if (standardTemplate != null) {
-                    InsOrderStandardTemplate insOrderStandardTemplate = new InsOrderStandardTemplate();
-                    insOrderStandardTemplate.setInsOrderId(orderId);
-                    insOrderStandardTemplate.setTemplateId(standardTemplate.getId());
-                    insOrderStandardTemplate.setNumber(standardTemplate.getNumber());
-                    insOrderStandardTemplate.setName(standardTemplate.getName());
-                    insOrderStandardTemplate.setThing(standardTemplate.getThing());
-                    insOrderStandardTemplateService.save(insOrderStandardTemplate);
-                }
+        List<InsOrderStandardTemplate> refreshedSnapshots = new ArrayList<>();
+        for (Integer templateId : templateIds) {
+            StandardTemplate standardTemplate = standardTemplateService.getById(templateId);
+            InsOrderStandardTemplate oldSnapshot = oldSnapshotMap.get(templateId);
+            if (standardTemplate == null && oldSnapshot == null) {
+                continue;
             }
+            InsOrderStandardTemplate refreshed = new InsOrderStandardTemplate();
+            refreshed.setInsOrderId(orderId);
+            refreshed.setTemplateId(templateId);
+            refreshed.setNumber(standardTemplate == null ? oldSnapshot.getNumber() : standardTemplate.getNumber());
+            refreshed.setName(standardTemplate == null ? oldSnapshot.getName() : standardTemplate.getName());
+            refreshed.setRemark(standardTemplate == null ? oldSnapshot.getRemark() : standardTemplate.getRemark());
+            refreshed.setThing(standardTemplate == null ? oldSnapshot.getThing() : standardTemplate.getThing());
+            refreshed.setRecordValues(templateRecordValuesMap.get(templateId));
+            refreshed.setTemperature(templateTemperatureMap.get(templateId));
+            refreshed.setHumidity(templateHumidityMap.get(templateId));
+            Integer templateSort = templateSortMap.get(templateId);
+            refreshed.setSort(templateSort == null ? nextTemplateSort++ : templateSort);
+            refreshedSnapshots.add(refreshed);
+        }
+
+        // 鏂板揩鐓у凡瀹屾暣缁勮鍚庡啀鏇挎崲锛涗簨鍔″け璐ユ椂浼氭暣浣撳洖婊氥��
+        insOrderStandardTemplateService.remove(Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                .eq(InsOrderStandardTemplate::getInsOrderId, orderId));
+        if (!refreshedSnapshots.isEmpty()) {
+            insOrderStandardTemplateService.saveBatch(refreshedSnapshots);
         }
     }
 
@@ -1151,6 +1550,44 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int rawMaterialVerifyPlan(Integer orderId, String laboratory, Integer type, String tell, Integer userId) {
+        VerifyPlanDto dto = new VerifyPlanDto();
+        dto.setOrderId(orderId);
+        dto.setLaboratory(laboratory);
+        dto.setType(type);
+        dto.setTell(tell);
+        dto.setUserId(userId);
+        return rawMaterialVerifyPlan(dto);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int rawMaterialVerifyPlan(VerifyPlanDto dto) {
+        Integer orderId = dto.getOrderId();
+        String laboratory = dto.getLaboratory();
+        Integer type = dto.getType();
+        String tell = dto.getTell();
+        Integer userId = dto.getUserId();
+        if (orderId == null || laboratory == null || type == null) {
+            throw new ErrorException("澶嶆牳鍙傛暟涓嶅畬鏁�");
+        }
+        ensureDirectTemplateOrderState(orderId);
+        InsOrderState currentState = insOrderStateMapper.selectOne(Wrappers.<InsOrderState>lambdaQuery()
+                .eq(InsOrderState::getInsOrderId, orderId)
+                .eq(InsOrderState::getLaboratory, laboratory)
+                .last("limit 1"));
+        InsReport existingReport = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
+                .eq(InsReport::getInsOrderId, orderId)
+                .last("limit 1"));
+        if (currentState != null && Objects.equals(currentState.getInsState(), 5)
+                && existingReport != null && StringUtils.isNotBlank(existingReport.getUrl())) {
+            // 闃叉澶嶆牳椤甸潰閲嶅鐐瑰嚮鎴栫綉缁滈噸璇曢噸澶嶇敓鎴愭姤鍛娿�侀噸澶嶅彂璧锋姤鍛婂鎵广��
+            return 1;
+        }
+        boolean directTemplateOrder = isDirectTemplateOrder(orderId);
+        if (directTemplateOrder && Objects.equals(type, 1)
+                && !Objects.equals(dto.getInsResult(), 0) && !Objects.equals(dto.getInsResult(), 1)) {
+            throw new ErrorException("璇烽�夋嫨妫�楠屽崟缁撹");
+        }
         Integer num = (type == 1 ? 5 : 4);
         LocalDateTime now = LocalDateTime.now();
         insOrderStateMapper.update(null, Wrappers.<InsOrderState>lambdaUpdate()
@@ -1164,31 +1601,65 @@
                 .eq(InsOrderState::getInsOrderId, orderId)
                 .ne(InsOrderState::getInsState, 5));
         InsReport report = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
-                .eq(InsReport::getInsOrderId, orderId));
+                .eq(InsReport::getInsOrderId, orderId)
+                .last("limit 1"));
+        Integer writeUserId = report != null && report.getWriteUserId() != null
+                ? report.getWriteUserId() : resolveInspectionUserId(orderId, laboratory);
         // 鏌ヨ璁㈠崟
         InsOrder order = insOrderMapper.selectById(orderId);
+        if (directTemplateOrder && Objects.equals(type, 1)) {
+            List<Integer> templateResults = insOrderStandardTemplateService.list(
+                            Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                                    .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
+                                    .select(InsOrderStandardTemplate::getInsResult))
+                    .stream().map(InsOrderStandardTemplate::getInsResult)
+                    .collect(Collectors.toList());
+            if (templateResults.isEmpty()
+                    || templateResults.stream().anyMatch(result -> !Objects.equals(result, 0)
+                            && !Objects.equals(result, 1))) {
+                throw new ErrorException("璇峰厛濉啓鍏ㄩ儴妯℃澘鐨勬楠岀粨璁�");
+            }
+            Integer aggregateResult = templateResults.stream().allMatch(result -> Objects.equals(result, 1))
+                    ? 1 : 0;
+            insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
+                    .eq(InsOrder::getId, orderId)
+                    .set(InsOrder::getInsResult, aggregateResult));
+            order.setInsResult(aggregateResult);
+        }
         if (count == 0 && num == 5) {
-            // 淇敼鎶ュ憡鐘舵��
-            insReportMapper.update(null, Wrappers.<InsReport>lambdaUpdate()
-                    .eq(InsReport::getInsOrderId, orderId)
-                    .set(InsReport::getIsPass, 1));
-
             // 淇敼璁㈠崟鐘舵��
             insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                     .eq(InsOrder::getId, orderId)
                     .set(InsOrder::getInsState, 5));
 
+            // 妫�楠屼汉鍛樻彁浜ゅ鏍告椂涓嶅啀鐢熸垚鎶ュ憡锛涗粎鍦ㄥ叏閮ㄨ瘯楠屽澶嶆牳閫氳繃鍚庣敓鎴愭娴嬫姤鍛娿��
+            if (report == null || StringUtils.isBlank(report.getUrl())) {
+                this.generateReport(orderId, DETECTION_REPORT, writeUserId);
+                report = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
+                        .eq(InsReport::getInsOrderId, orderId)
+                        .last("limit 1"));
+            }
+            if (report == null || StringUtils.isBlank(report.getUrl())) {
+                throw new ErrorException("妫�娴嬫姤鍛婄敓鎴愬け璐�");
+            }
+            if (report.getTempUrlPdf() == null) {
+                String tempUrlPdf = this.wordToPdfTemp(report.getUrl().replace("/word", wordUrl));
+                report.setTempUrlPdf("/word/" + tempUrlPdf);
+            }
+            report.setIsPass(1);
+            insReportMapper.updateById(report);
+
             //鐢熸垚鍘熸潗鏂欒繘璐ч獙璇佸師濮嬭褰曞埌闄勪欢閲�
             if (order.getTypeSource() != null && order.getTypeSource().equals(1)) {
-                this.reportFactoryVerify(orderId, userId, report.getWriteUserId());
+                this.reportFactoryVerify(orderId, userId, writeUserId);
             }
 
             // 鏌ヨ妫�楠屼换鍔$殑妫�楠屼换鍔�
             // 鎶ュ憡鐩栦笂鎵瑰噯浜�
-            insReportService.writeReport(report.getId(), userId, report.getWriteUserId());
+            insReportService.writeReport(report.getId(), userId, writeUserId);
 
             // 妫�楠屼汉
-            String userName = insProductMapper.selectUserById(report.getWriteUserId()).get("name");
+            String userName = insProductMapper.selectUserById(writeUserId).get("name");
 
             // 澶嶆牳浜�
             Integer checkUserId = SecurityUtils.getUserId().intValue();
@@ -1237,7 +1708,7 @@
 
             // 鍙戦�佹秷鎭殑浜�
             // 鏌ヨ鍙戦�佷汉淇℃伅
-            String sendUserAccount = insProductMapper.selectUserById(report.getWriteUserId()).get("account");
+            String sendUserAccount = insProductMapper.selectUserById(writeUserId).get("account");
 
             // 鍙戦�佷紒涓氬井淇¢�氱煡(妫�楠屼换鍔¢��鍥�)
             threadPoolTaskExecutor.execute(() -> {
@@ -1269,11 +1740,63 @@
         return 1;
     }
 
+    private boolean isDirectTemplateOrder(Integer orderId) {
+        List<InsSample> samples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery()
+                .eq(InsSample::getInsOrderId, orderId));
+        if (samples.isEmpty()) {
+            return false;
+        }
+        Long snapshotCount = insOrderStandardTemplateService.count(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId));
+        InsOrder order = snapshotCount == 0 ? null : insOrderMapper.selectById(orderId);
+        if (order != null && samples.stream().anyMatch(sample -> isDirectTemplateSample(order, sample))) {
+            return true;
+        }
+        // 鍏煎宸茬粡鐢熸垚浜嗘ā鏉胯鍙枫�佷絾璁㈠崟鍩虹瀛楁涓嶅畬鏁寸殑鍘嗗彶鐩寸粦璁㈠崟銆�
+        return insProductMapper.selectCount(Wrappers.<InsProduct>lambdaQuery()
+                .in(InsProduct::getInsSampleId, samples.stream().map(InsSample::getId).collect(Collectors.toList()))
+                .isNotNull(InsProduct::getTemplateRowIndex)) > 0;
+    }
+
+    /**
+     * 鍏煎淇涓婄嚎鍓嶅凡缁忎笅鍙戙�佷絾鍥犳病鏈� ins_product 鑰岀己灏戜换鍔$姸鎬佺殑鐩寸粦妯℃澘璁㈠崟銆�
+     */
+    private void ensureDirectTemplateOrderState(Integer orderId) {
+        if (!isDirectTemplateOrder(orderId)) {
+            return;
+        }
+        Long stateCount = insOrderStateMapper.selectCount(Wrappers.<InsOrderState>lambdaQuery()
+                .eq(InsOrderState::getInsOrderId, orderId)
+                .eq(InsOrderState::getLaboratory, DIRECT_TEMPLATE_LABORATORY));
+        if (stateCount > 0) {
+            return;
+        }
+        InsOrderState state = new InsOrderState();
+        state.setInsOrderId(orderId);
+        state.setLaboratory(DIRECT_TEMPLATE_LABORATORY);
+        state.setInsState(0);
+        insOrderStateMapper.insert(state);
+    }
+
+    private Integer resolveInspectionUserId(Integer orderId, String laboratory) {
+        InsSampleUser inspector = insSampleUserMapper.selectOne(Wrappers.<InsSampleUser>lambdaQuery()
+                .eq(InsSampleUser::getInsSampleId, orderId)
+                .eq(InsSampleUser::getState, 0)
+                .eq(StringUtils.isNotBlank(laboratory), InsSampleUser::getSonLaboratory, laboratory)
+                .orderByDesc(InsSampleUser::getId)
+                .last("limit 1"));
+        if (inspector == null || inspector.getUserId() == null) {
+            throw new ErrorException("鎵句笉鍒板綋鍓嶆楠屼换鍔$殑妫�楠屼汉锛屾棤娉曠敓鎴愭娴嬫姤鍛�");
+        }
+        return inspector.getUserId();
+    }
+
     /**
      * 鐢熸垚鎶ュ憡
      * @param orderId
      */
-    private void generateReport(Integer orderId, Integer reportType) {
+    private void generateReport(Integer orderId, Integer reportType, Integer writeUserId) {
         /*鏍峰搧涓嬬殑椤圭洰鍙鏈変竴涓」鐩笉鍚堟牸鍒欐楠岀粨鏋滀负0,鍚﹀垯涓�1*/
         //杩欓噷鐨刬nsSamples鏄鍗曚笅鐨勬墍鏈夋牱鍝佸寘鎷�("/")
         List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery().eq(InsSample::getInsOrderId, orderId));
@@ -1301,10 +1824,10 @@
         // 鍒ゆ柇鏄ぇ鎶ュ憡杩樻槸灏忔姤鍛�  杩涘巶灏忔姤鍛�, 鍏朵粬閮芥槸澶ф姤鍛�
         if (insOrder.getOrderType().equals(InsOrderTypeConstants.ENTER_THE_FACTORY)) {
             // 鐢熸垚灏忔姤鍛�
-            addSmallReport(orderId, insOrder, insSamples, reportType);
+            addSmallReport(orderId, insOrder, insSamples, reportType, writeUserId);
         } else {
             //鐢熸垚澶ф姤鍛�
-            addBitReport(orderId, insOrder, reportType);
+            addBitReport(orderId, insOrder, reportType, writeUserId);
         }
 
     }
@@ -1403,7 +1926,7 @@
             return null;
         }
         InsOrder order = insOrderMapper.selectFirstSubmit(dto.getId());
-        getTemplateThing(order, Collections.unmodifiableList(insProducts));
+        getTemplateThing(order, Collections.unmodifiableList(insProducts), dto.getLaboratory());
         return insProducts;
     }
 
@@ -1750,7 +2273,7 @@
      * @param insOrder
      * @param insSamples
      */
-    private void addSmallReport(Integer orderId, InsOrder insOrder, List<InsSample> insSamples, Integer reportType) {
+    private void addSmallReport(Integer orderId, InsOrder insOrder, List<InsSample> insSamples, Integer reportType, Integer writeUserId) {
         InsReport insReport = new InsReport();
         AtomicReference<String> resultCh = new AtomicReference<>("");
         boolean showResult = Objects.equals(reportType, INSPECTION_REPORT);
@@ -1939,15 +2462,13 @@
             // 淇敼鎹㈣鍜屽悎骞堕棶棰�
             updaeMerge(template.getXWPFDocument(), true);
             String name = insReport.getCode().replace("/", "") + (showResult ? "-J.docx" : "-C.docx");
-            template.writeAndClose(Files.newOutputStream(Paths.get(wordUrl + "/" + name)));
+            Files.createDirectories(Paths.get(wordUrl));
+            template.writeAndClose(Files.newOutputStream(Paths.get(wordUrl, name)));
             insReport.setUrl("/word/" + name);
             insReport.setIsPass(0);
-            insReport.setWriteUserId(SecurityUtils.getUserId().intValue());//鎻愪氦浜�
+            insReport.setWriteUserId(writeUserId);//缂栧埗浜猴紙妫�楠屼汉锛�
             insReport.setWriteTime(LocalDateTime.now());//鎻愪氦鏃堕棿
-            // 鏌ヨ鎶ュ憡, 鍒ゆ柇涔嬪墠鏄惁娣诲姞杩�, 娣诲姞杩囧垹闄�
-            insReportMapper.delete(Wrappers.<InsReport>lambdaQuery()
-                    .eq(InsReport::getInsOrderId, insOrder.getId()));
-            insReportMapper.insert(insReport);
+            saveGeneratedReport(insReport);
             inputStream.close();
         } catch (IOException e) {
             throw new RuntimeException(e);
@@ -2442,7 +2963,7 @@
      * @param insOrder
      * @param
      */
-    private void addBitReport(Integer orderId, InsOrder insOrder, Integer reportType) {
+    private void addBitReport(Integer orderId, InsOrder insOrder, Integer reportType, Integer writeUserId) {
         //samples鏄笉鍖呮嫭甯︽湁"/"鐨勬牱鍝�
         List<SampleProductDto> samples = insSampleMapper.selectSampleProductListByOrderId(orderId);
         InsReport insReport = new InsReport();
@@ -2479,6 +3000,11 @@
                         }
                     }
                 }
+            }
+            // 鏂扮増妫�娴嬫姤鍛婄洿鎺ヤ娇鐢ㄥ浐瀹氬洓鍒楃粨鏋滆锛屼笉鍐嶆瀯閫犳棫鐗堝彲鍙樺垪 TableRenderData銆�
+            // 妫�楠屾姤鍛婁粛娌跨敤涓嬫柟鍘熸湁鐨勫ぇ琛ㄦ牸閫昏緫銆�
+            if (!showResult) {
+                return;
             }
             // 鏀舵牱鏃ユ湡
             List<RowRenderData> rows = new ArrayList<>();
@@ -2526,12 +3052,16 @@
                         }
                     }
                 }
-                insOrder.setSampleView(insOrder.getSample());
-                String sampleViewEn = insSampleMapper.getSampleEn(insOrder.getSample());
-                if (StringUtils.isBlank(sampleViewEn)) {
-                    sampleViewEn = insSampleMapper.getSampleEnByObject(insOrder.getSample());
+                if (StringUtils.isBlank(insOrder.getSampleView())) {
+                    insOrder.setSampleView(insOrder.getSample());
                 }
-                insOrder.setSampleViewEn(sampleViewEn);
+                if (StringUtils.isBlank(insOrder.getSampleViewEn())) {
+                    String sampleViewEn = insSampleMapper.getSampleEn(insOrder.getSample());
+                    if (StringUtils.isBlank(sampleViewEn)) {
+                        sampleViewEn = insSampleMapper.getSampleEnByObject(insOrder.getSample());
+                    }
+                    insOrder.setSampleViewEn(sampleViewEn);
+                }
             } else {
                 // 鑾峰緱鎵归噺妫�楠岀殑鎬绘暟
                 max = insOrderMapper.selectSampleMax(a.getId());
@@ -2671,15 +3201,26 @@
         for (String s : standardMethod) {
             standardMethod2.append("銆�").append(s);
         }
-        standardMethod2.replace(0, 1, "");
+        if (standardMethod2.length() > 0) {
+            standardMethod2.deleteCharAt(0);
+        }
         tables.forEach(table -> {
             table.put("tableSize", tables.size() + 1);
         });
-        // 璁惧淇℃伅
-        List<Map<String, String>> deviceList = null;
-        if (CollectionUtils.isNotEmpty(deviceSet)) {
-            deviceList = insOrderMapper.selectDeviceList(deviceSet);
+        // 鏂拌鍗曟寜妯℃澘閫夋嫨璁惧锛涙棫璁㈠崟娌℃湁妯℃澘璁惧鍏崇郴鏃跺洖閫�鍒拌鍗曠骇璁惧璁板綍銆�
+        Set<String> reportDeviceNumbers = insOrderTemplateDeviceMapper
+                .selectManagementNumbersByOrderId(orderId).stream()
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        if (reportDeviceNumbers.isEmpty()) {
+            reportDeviceNumbers.addAll(insOrderDeviceRecordMapper.selectDeviceNumber(orderId).stream()
+                    .map(InsOrderDeviceRecordDto::getManagementNumber)
+                    .filter(StringUtils::isNotBlank)
+                    .collect(Collectors.toList()));
         }
+        reportDeviceNumbers.addAll(deviceSet);
+        List<Map<String, String>> deviceList = reportDeviceNumbers.isEmpty()
+                ? new ArrayList<>() : insOrderMapper.selectDeviceList(reportDeviceNumbers);
         if (CollectionUtils.isNotEmpty(deviceList)) {
             int count = 1;
             for (Map<String, String> stringMap : deviceList) {
@@ -2690,7 +3231,7 @@
 
         Map<String, String> codeStr = new HashMap<>();
         codeStr.put("鎶ュ憡缂栧彿", insReport.getCode());
-        codeStr.put("鏍峰搧鍚嶇О", insOrder.getSample());
+        codeStr.put("鏍峰搧鍚嶇О", defaultReportText(insOrder.getSampleView(), insOrder.getSample()));
         codeStr.put("瑙勬牸鍨嬪彿", samples.get(0).getModel());
         codeStr.put("鍙戞斁鏃ユ湡", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
 
@@ -2707,9 +3248,11 @@
         // 鏉ユ牱鏂瑰紡
         String formType = iSysDictTypeService.selectLabelByDict(DictDataConstants.FORM_TYPE, insOrder.getFormType());
 
-        // 鏍峰搧鐘舵��
-        String sampleStatus = iSysDictTypeService.selectLabelByDict(DictDataConstants.SAMPLE_STATUS_LIST, insOrder.getSampleStatus());
-        ;
+        // 鏍峰搧澶栬浼樺厛灞曠ず瀛楀吀鍚嶇О锛涘巻鍙叉暟鎹垨鐩存帴淇濆瓨鏂囨湰鏃讹紝鍥為��灞曠ず璁㈠崟鍘熷�笺��
+        String sampleStatus = defaultReportText(
+                iSysDictTypeService.selectLabelByDict(
+                        DictDataConstants.SAMPLE_STATUS_LIST, insOrder.getSampleStatus()),
+                insOrder.getSampleStatus());
 
         // 鍏徃淇℃伅
         Custom custom = customMapper.selectById(insOrder.getCompanyId());
@@ -2775,13 +3318,16 @@
         // 鎶芥牱鏃ユ湡
         // 鎴愬搧鏄娊鏍锋椂闂�
         LocalDateTime sendTime = insOrder.getSendTime();
-        if (insOrder.getTypeSource() != null && insOrder.getTypeSource().equals(1)) {
+        IfsInventoryQuantity reportInventory = null;
+        if (insOrder.getIfsInventoryId() != null) {
+            reportInventory = ifsInventoryQuantityMapper.selectById(insOrder.getIfsInventoryId());
+        }
+        if (insOrder.getTypeSource() != null && insOrder.getTypeSource().equals(1) && reportInventory != null) {
             // 鍘熸潗鏂欐敹鏍锋棩鏈熸槸鎶ユ鏃ユ湡
-            IfsInventoryQuantity ifsInventoryQuantity = ifsInventoryQuantityMapper.selectById(insOrder.getIfsInventoryId());
-            sendTime = ifsInventoryQuantity.getDeclareDate();
+            sendTime = reportInventory.getDeclareDate();
         }
 
-        // 妫�楠屾椂闂�  鎶芥牱鏃堕棿-鎻愪氦鏃堕棿
+        // 妫�娴嬫棩鏈燂細棣栨淇濆瓨妯℃澘鍊兼椂闂�-棣栨鎻愪氦鏃堕棿
         LocalDateTime now = LocalDateTime.now();
         // 鎻愪氦鏃堕棿
         LocalDateTime submitTime = LocalDateTime.now();
@@ -2790,15 +3336,20 @@
             submitTime = insOrder.getFirstSubmitDate();
         }
 
-        String insTime = insOrder.getSendTime().format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�")) + "-"
+        LocalDateTime firstInspectDate = insOrder.getFirstInspectDate();
+        // 瀛楁涓婄嚎鍓嶇殑鍘嗗彶璁㈠崟娌跨敤鍘熸潵鐨勪笅鍙戞椂闂达紝閬垮厤鏀瑰彉鏃㈡湁鎶ュ憡鍙e緞銆�
+        if (firstInspectDate == null) {
+            firstInspectDate = insOrder.getSendTime();
+        }
+
+        String insTime = firstInspectDate.format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�")) + "-"
                 + submitTime.format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�"));
 
-        String insTimeEn = monthNames[insOrder.getSendTime().getMonthValue() - 1] + " " + insOrder.getSendTime().format(DateTimeFormatter.ofPattern("dd, yyyy")) + "-"
+        String insTimeEn = monthNames[firstInspectDate.getMonthValue() - 1] + " " + firstInspectDate.format(DateTimeFormatter.ofPattern("dd, yyyy")) + "-"
                 + monthNames[submitTime.getMonthValue() - 1] + " " + submitTime.format(DateTimeFormatter.ofPattern("dd, yyyy"));
 
-        //妫�楠岄」鐩殑鐜
-        String environment = "";
-        environment = (ObjectUtils.isNotEmpty(insOrder.getTemperature()) ? insOrder.getTemperature() + "鈩� " : "") + (ObjectUtils.isNotEmpty(insOrder.getHumidity()) ? insOrder.getHumidity() + "%" : "");
+        // 妫�楠岀幆澧冿細浼樺厛姹囨�诲悇妯℃澘濉啓鐨勬俯婀垮害锛屾姤鍛婂睍绀哄叏閮ㄦā鏉跨殑鏈�灏忓�煎埌鏈�澶у�笺��
+        String environment = buildReportEnvironment(orderId, insOrder);
         String finalEnvironment = environment;
         LocalDateTime finalSendTime = sendTime;
         String finalResultCh = resultCh;
@@ -2807,12 +3358,85 @@
         List<Map<String, String>> finalDeviceList = deviceList;
         String finalModelStr = modelStr;
 
-        InputStream inputStream = this.getClass().getResourceAsStream(showResult ? "/static/report-template.docx" : "/static/detection-report-template.docx");
+        List<Map<String, String>> detectionResultRows = buildDetectionResultRows(orderId, samples);
+        String productName = defaultReportText(insOrder.getSampleView(), insOrder.getSample());
+        String customerName = defaultReportText(custom == null ? null : custom.getCompany(), insOrder.getCompany());
+        String detectionCategory = defaultReportText(finalOrderType, "濮旀墭妫�娴�");
+        String sampleCode = samples.stream()
+                .map(SampleProductDto::getSampleCode)
+                .filter(StringUtils::isNotBlank)
+                .distinct()
+                .collect(Collectors.joining("銆�"));
+        String productAndModel = productName;
+        if (StringUtils.isNotBlank(finalModelStr) && !Objects.equals(productName, finalModelStr)) {
+            productAndModel = productName + " " + finalModelStr;
+        }
+        String standardMethodText = "null".equals(standardMethod2.toString()) ? "" : standardMethod2.toString();
+        String productionDate = "/";
+        String batchNo = "/";
+        if (reportInventory != null) {
+            if (reportInventory.getProductDate() != null) {
+                productionDate = reportInventory.getProductDate().format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�"));
+            }
+            batchNo = defaultReportText(reportInventory.getUpdateBatchNo(), reportInventory.getLotBatchNo());
+        }
+        // 鏂拌仛鐩寸粦妯℃澘璁㈠崟锛氭姤鍛婁俊鎭彇濮旀墭淇℃伅鍜岃鍗曟ā鏉垮揩鐓э紝涓嶅啀鍙栬兘鍔涜寖鍥存爣鍑嗘柟娉曘��
+        List<InsOrderStandardTemplate> templateSnapshots = insOrderStandardTemplateService.list(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
+                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
+        boolean newJuTemplateOrder = !templateSnapshots.isEmpty() && isDirectTemplateOrder(orderId);
+        if (newJuTemplateOrder) {
+            productName = defaultReportText(insOrder.getSampleView(), insOrder.getSample());
+            finalModelStr = "/";
+            productAndModel = productName;
+            if (insOrder.getSampleDate() != null) {
+                finalSendTime = insOrder.getSampleDate().atStartOfDay();
+            }
+            if (insOrder.getProductionDate() != null) {
+                productionDate = insOrder.getProductionDate().format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�"));
+            }
+            batchNo = defaultReportText(insOrder.getProductionBatch(), "/");
+            standardMethodText = templateSnapshots.stream()
+                    .map(InsOrderStandardTemplate::getRemark)
+                    .filter(StringUtils::isNotBlank)
+                    .distinct()
+                    .collect(Collectors.joining("锛�"));
+        }
+        boolean containsOrganicChlorine = detectionResultRows.stream()
+                .map(row -> row.get("itemName"))
+                .filter(Objects::nonNull)
+                .anyMatch(name -> name.contains("鏈夋満姘�"));
+        String resultRemark = containsOrganicChlorine
+                ? "寰簱浠戞硶锛屾湁鏈烘隘鍚噺灏忎簬0.005%鏃讹紝浠ユ湭妫�鍑烘姤鍑恒��"
+                : defaultReportText(insOrder.getRemark(), "/");
+
+        // 妫�娴嬫姤鍛婂拰妫�楠屾姤鍛婄粺涓�浣跨敤鏂扮増鎶ュ憡妯℃澘
+        String templateName = "/static/detection-report-template-v2.docx";
+        InputStream inputStream = this.getClass().getResourceAsStream(templateName);
+        if (inputStream == null) {
+            throw new RuntimeException("鎵句笉鍒版姤鍛婃ā鏉�: " + templateName);
+        }
         Configure configure = Configure.builder()
                 .bind("deviceList", new HackLoopTableRenderPolicy())
+                .bind("r", new HackLoopTableRenderPolicy(true))
+                .setValidErrorHandler(new PreserveReportPicturePlaceholderHandler())
                 .useSpringEL(true)
                 .build();
 
+        String finalProductAndModel = productAndModel;
+        String finalProductName = productName;
+        String finalCustomerName = customerName;
+        String finalDetectionCategory = detectionCategory;
+        String finalSampleCode = defaultReportText(sampleCode, "/");
+        String finalStandardMethodText = defaultReportText(standardMethodText, "/");
+        String finalProductionDate = productionDate;
+        String finalBatchNo = batchNo;
+        String finalResultRemark = resultRemark;
+        String equipmentText = buildEquipmentText(finalDeviceList);
+        String issueDate = now.format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�"));
+        String finalReportModelStr = finalModelStr;
+        LocalDateTime finalReportSendTime = finalSendTime;
         XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(
                 new HashMap<String, Object>() {{
                     put("order", insOrder);
@@ -2822,10 +3446,10 @@
                     put("sampleSize", insOrder.getTestQuantity());
                     put("tables", tables);
                     put("tableSize", tables.size() + 1);
-                    put("standardMethod", (standardMethod2.toString().equals("null") ? "" : standardMethod2));
+                    put("standardMethod", finalStandardMethodText);
                     put("deviceList", finalDeviceList);
                     put("twoCode", null);
-                    put("models", finalModelStr);
+                    put("models", finalReportModelStr);
                     put("productSize", productSize.get());
                     put("createTime", now.format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�")));
                     put("createTimeEn", monthNames[now.getMonthValue() - 1] + " " + now.format(DateTimeFormatter.ofPattern("dd, yyyy")));
@@ -2837,8 +3461,8 @@
                     put("examineUrl", null);
                     put("ratifyUrl", null);
                     put("orderType", finalOrderType);
-                    put("getTime", finalSendTime.format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�")));
-                    put("getTimeEn", monthNames[finalSendTime.getMonthValue() - 1] + " " + finalSendTime.format(DateTimeFormatter.ofPattern("dd, yyyy")));
+                    put("getTime", finalReportSendTime.format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�")));
+                    put("getTimeEn", monthNames[finalReportSendTime.getMonthValue() - 1] + " " + finalReportSendTime.format(DateTimeFormatter.ofPattern("dd, yyyy")));
                     put("seal1", null);
                     put("seal2", null);
                     put("formTypeCh", formType);
@@ -2846,23 +3470,320 @@
                     put("resultCh", finalResultCh);
                     put("resultEn", finalResultEn);
                     put("sampleStatus", sampleStatus);
+                    // 鏂扮増妫�娴嬫姤鍛婃ā鏉匡紙鍙傜収銆婃湁鏈烘隘妫�娴嬫姤鍛娿�嬶級浣跨敤鐨勬墎骞冲瓧娈点��
+                    put("no", insReport.getCode());
+                    put("cover", finalProductAndModel);
+                    put("product", finalProductName);
+                    put("model", defaultReportText(finalReportModelStr, "/"));
+                    put("customer", finalCustomerName);
+                    put("category", finalDetectionCategory);
+                    put("date", issueDate);
+                    put("maker", defaultReportText(insOrder.getProduction(), "/"));
+                    put("env", finalEnvironment);
+                    put("qty", defaultReportText(insOrder.getTestQuantity(), "/"));
+                    put("receive", finalReportSendTime.format(DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�")));
+                    put("sampleNo", finalSampleCode);
+                    put("appearance", defaultReportText(sampleStatus, "/"));
+                    put("sender", defaultReportText(insOrder.getPrepareUser(), "/"));
+                    put("produced", finalProductionDate);
+                    put("batch", finalBatchNo);
+                    put("phone", defaultReportText(insOrder.getPhone(), "/"));
+                    put("testDate", insTime);
+                    put("entrustNo", defaultReportText(insOrder.getEntrustCode(), "/"));
+                    put("sampling", "/");
+                    put("standard", finalStandardMethodText);
+                    put("equipment", equipmentText);
+                    put("conclusion", "鏍峰搧鎸夊鎵樻柟瑕佹眰杩涜妫�娴嬶紝鏁版嵁瑙佹娴嬬粨鏋溿��");
+                    put("remark", "鏈姤鍛婃娴嬬粨鏋滀粎瀵瑰綋鏃ユ墍鎺ユ敹鏍峰搧璐熻矗銆�");
+                    put("r", detectionResultRows);
+                    put("resultNote", finalResultRemark);
                 }});
         try {
-            // 淇敼鎹㈣鍜屽悎骞堕棶棰�
-            updaeMerge(template.getXWPFDocument(), false);
             String name = insReport.getCode().replace("/", "") + (showResult ? ".docx" : "-C.docx");
-            template.writeAndClose(Files.newOutputStream(Paths.get(wordUrl + "/" + name)));
+            Files.createDirectories(Paths.get(wordUrl));
+            template.writeAndClose(Files.newOutputStream(Paths.get(wordUrl, name)));
             insReport.setUrl("/word/" + name);
-            insReport.setWriteUserId(SecurityUtils.getUserId().intValue());//鎻愪氦浜�
+            insReport.setWriteUserId(writeUserId);//缂栧埗浜猴紙妫�楠屼汉锛�
             insReport.setWriteTime(LocalDateTime.now());//鎻愪氦鏃堕棿
-            // 鏌ヨ鎶ュ憡, 鍒ゆ柇涔嬪墠鏄惁娣诲姞杩�, 娣诲姞杩囧垹闄�
-            insReportMapper.delete(Wrappers.<InsReport>lambdaQuery()
-                    .eq(InsReport::getInsOrderId, insOrder.getId()));
-            insReportMapper.insert(insReport);
+            saveGeneratedReport(insReport);
             inputStream.close();
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
+    }
+
+    private void saveGeneratedReport(InsReport report) {
+        InsReport existing = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
+                .eq(InsReport::getInsOrderId, report.getInsOrderId())
+                .last("limit 1"));
+        if (existing == null) {
+            insReportMapper.insert(report);
+            return;
+        }
+        report.setId(existing.getId());
+        insReportMapper.updateById(report);
+    }
+
+    /**
+     * 鏂扮増妫�娴嬫姤鍛婄殑缁撴灉琛ㄥ浐瀹氫负鍥涘垪锛氬簭鍙枫�佹娴嬮」鐩�佸崟浣嶃�佹娴嬬粨鏋溿��
+     */
+    private List<Map<String, String>> buildDetectionResultRows(Integer orderId, List<SampleProductDto> samples) {
+        if (isDirectTemplateOrder(orderId)) {
+            return buildTemplateDetectionResultRows(orderId);
+        }
+        List<Map<String, String>> rows = new ArrayList<>();
+        if (CollectionUtils.isEmpty(samples)) {
+            return rows;
+        }
+
+        boolean multipleSamples = samples.size() > 1;
+        int sequence = 1;
+        for (SampleProductDto sample : samples) {
+            if (CollectionUtils.isEmpty(sample.getInsProduct())) {
+                continue;
+            }
+            for (InsProduct product : sample.getInsProduct()) {
+                // 缁戝畾椤圭洰浠呭弬涓庤绠楋紝涓嶅簲鍑虹幇鍦ㄦ姤鍛婁腑銆�
+                if (product == null || Objects.equals(product.getIsBinding(), 1)) {
+                    continue;
+                }
+
+                String itemName = defaultReportText(
+                        product.getInspectionItemSubclass(),
+                        defaultReportText(product.getInspectionItem(), product.getInspectionItemClass()));
+                if (multipleSamples && StringUtils.isNotBlank(sample.getSampleCode())) {
+                    itemName = sample.getSampleCode() + " - " + itemName;
+                }
+
+                Map<String, String> row = new LinkedHashMap<>();
+                row.put("seq", String.valueOf(sequence++));
+                row.put("itemName", itemName);
+                row.put("unit", defaultReportText(product.getUnit(), "/"));
+                row.put("result", defaultReportText(product.getLastValue(), "/"));
+                rows.add(row);
+            }
+        }
+        return rows;
+    }
+
+    /**
+     * 鏂拌仛鐩寸粦妯℃澘璁㈠崟锛氫竴涓ā鏉块〉绛惧湪鎶ュ憡涓彧瀵瑰簲涓�鏉℃娴嬮」鐩��
+     */
+    private List<Map<String, String>> buildTemplateDetectionResultRows(Integer orderId) {
+        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateService.list(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
+                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
+        List<Map<String, String>> rows = new ArrayList<>();
+        int sequence = 1;
+        for (InsOrderStandardTemplate snapshot : snapshots) {
+            TemplateExportCell exportCell = getFirstTemplateExportCell(snapshot.getThing());
+            JSONObject recordValues = StrUtil.isBlank(snapshot.getRecordValues())
+                    ? new JSONObject() : JSON.parseObject(snapshot.getRecordValues());
+            String result = readTemplateCellText(recordValues, exportCell.resultCoordinate, exportCell.templateValues);
+            if (StringUtils.isBlank(result)) {
+                throw new ErrorException("妯℃澘鈥�" + defaultReportText(snapshot.getName(), "妯℃澘ID " + snapshot.getTemplateId())
+                        + "鈥濈殑瀵煎嚭鍊间笉鑳戒负绌�");
+            }
+            String unit = exportCell.unitCoordinate == null ? null
+                    : readTemplateCellText(recordValues, exportCell.unitCoordinate, exportCell.templateValues);
+            String itemName = exportCell.itemCoordinate == null ? null
+                    : readTemplateCellText(recordValues, exportCell.itemCoordinate, exportCell.templateValues);
+
+            Map<String, String> row = new LinkedHashMap<>();
+            row.put("seq", String.valueOf(sequence++));
+            String reportItemName = defaultReportText(itemName,
+                    defaultReportText(snapshot.getName(), "妯℃澘ID " + snapshot.getTemplateId()));
+            row.put("itemName", formatExportItemName(reportItemName));
+            row.put("unit", defaultReportText(unit, "/"));
+            row.put("result", result);
+            rows.add(row);
+        }
+        if (rows.isEmpty()) {
+            throw new ErrorException("褰撳墠璁㈠崟鏈壘鍒板彲鐢熸垚鎶ュ憡鐨勫師濮嬭褰曟ā鏉�");
+        }
+        return rows;
+    }
+
+    private TemplateExportCell getFirstTemplateExportCell(String compressedThing) {
+        if (StrUtil.isBlank(compressedThing)) {
+            throw new ErrorException("鍘熷璁板綍妯℃澘涓虹┖");
+        }
+        String thing;
+        try {
+            thing = GZipUtil.uncompress(compressedThing);
+        } catch (Exception ignored) {
+            thing = compressedThing;
+        }
+        JSONArray cellData = JSON.parseObject(thing).getJSONArray("data").getJSONObject(0).getJSONArray("celldata");
+        Integer resultColumn = null;
+        Integer itemColumn = null;
+        Integer unitColumn = null;
+        List<int[]> exportCells = new ArrayList<>();
+        Map<String, String> templateValues = new HashMap<>();
+        for (Object item : cellData) {
+            JSONObject cell = JSON.parseObject(JSON.toJSONString(item));
+            Integer row = cell.getInteger("r");
+            Integer column = cell.getInteger("c");
+            JSONObject value = cell.getJSONObject("v");
+            if (row == null || column == null || value == null) {
+                continue;
+            }
+            String displayValue = value.get("v") == null ? null : String.valueOf(value.get("v"));
+            templateValues.put(row + "-" + column, displayValue);
+            if (resultColumn == null && "妫�楠岀粨鏋�".equals(StringUtils.trim(displayValue))) {
+                resultColumn = column;
+            }
+            if (itemColumn == null && ("妫�娴嬮」鐩�".equals(StringUtils.trim(displayValue))
+                    || "妫�楠岄」鐩�".equals(StringUtils.trim(displayValue)))) {
+                itemColumn = column;
+            }
+            if (unitColumn == null && "鍗曚綅".equals(StringUtils.trim(displayValue))) {
+                unitColumn = column;
+            }
+            JSONObject ps = value.getJSONObject("ps");
+            if (ps != null && "瀵煎嚭鍊�".equals(ps.getString("value"))) {
+                exportCells.add(new int[]{row, column});
+            }
+        }
+        if (exportCells.isEmpty()) {
+            throw new ErrorException("妯℃澘蹇呴』鑷冲皯閰嶇疆涓�涓鍑哄�兼壒娉�");
+        }
+        exportCells.sort(Comparator.comparingInt((int[] cell) -> cell[0]).thenComparingInt(cell -> cell[1]));
+        int[] firstExport = exportCells.get(0);
+        int resultColumnForRow = resultColumn == null ? firstExport[1] : resultColumn;
+        // 閫氬父鈥滃鍑哄�尖�濇壒娉ㄩ厤缃湪椤圭洰鍚嶇О鍗曞厓鏍硷紱鑻ラ厤缃湪缁撴灉鍗曞厓鏍硷紝鍒欏洖鍒扳�滄娴嬮」鐩�濆垪鍙栧悕绉般��
+        Integer itemColumnForRow = !Objects.equals(firstExport[1], resultColumnForRow)
+                ? firstExport[1] : itemColumn;
+        TemplateExportCell result = new TemplateExportCell();
+        result.resultCoordinate = firstExport[0] + "-" + resultColumnForRow;
+        result.itemCoordinate = itemColumnForRow == null ? null : firstExport[0] + "-" + itemColumnForRow;
+        result.unitCoordinate = unitColumn == null ? null : firstExport[0] + "-" + unitColumn;
+        result.templateValues = templateValues;
+        return result;
+    }
+
+    private String readTemplateCellText(JSONObject recordValues, String coordinate, Map<String, String> templateValues) {
+        JSONObject recordCell = recordValues == null ? null : recordValues.getJSONObject(coordinate);
+        if (recordCell != null && recordCell.get("v") != null) {
+            String value = String.valueOf(recordCell.get("v"));
+            if (StringUtils.isNotBlank(value)) {
+                return value;
+            }
+        }
+        return templateValues.get(coordinate);
+    }
+
+    private static class TemplateExportCell {
+        private String itemCoordinate;
+        private String resultCoordinate;
+        private String unitCoordinate;
+        private Map<String, String> templateValues;
+    }
+
+    private static String buildEquipmentText(List<Map<String, String>> deviceList) {
+        if (CollectionUtils.isEmpty(deviceList)) {
+            return "/";
+        }
+        Set<String> lines = new LinkedHashSet<>();
+        int sequence = 1;
+        for (Map<String, String> device : deviceList) {
+            String name = getMapText(device, "deviceName", "device_name");
+            String managementNumber = getMapText(device, "managementNumber", "management_number");
+            String line = sequence++ + ". " + defaultReportText(name, "/");
+            if (StringUtils.isNotBlank(managementNumber)) {
+                line += "锛�" + managementNumber;
+            }
+            lines.add(line);
+        }
+        return String.join("\n", lines);
+    }
+
+    private String buildReportEnvironment(Integer orderId, InsOrder order) {
+        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateService.list(
+                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
+                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
+                        .select(InsOrderStandardTemplate::getTemperature,
+                                InsOrderStandardTemplate::getHumidity));
+        List<String> temperatures = snapshots.stream()
+                .map(InsOrderStandardTemplate::getTemperature)
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.toList());
+        List<String> humidities = snapshots.stream()
+                .map(InsOrderStandardTemplate::getHumidity)
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.toList());
+
+        String temperature = formatConditionRange(temperatures, order.getTemperature(), "鈩�");
+        String humidity = formatConditionRange(humidities, order.getHumidity(), "%");
+        return Stream.of(temperature, humidity)
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.joining(" "));
+    }
+
+    private static String formatConditionRange(List<String> values, String fallback, String unit) {
+        List<BigDecimal> numbers = new ArrayList<>();
+        List<String> candidates = CollectionUtils.isEmpty(values)
+                ? Collections.singletonList(fallback) : values;
+        Pattern numberPattern = Pattern.compile("-?\\d+(?:\\.\\d+)?");
+        for (String value : candidates) {
+            if (StringUtils.isBlank(value)) {
+                continue;
+            }
+            java.util.regex.Matcher matcher = numberPattern.matcher(value);
+            while (matcher.find()) {
+                numbers.add(new BigDecimal(matcher.group()));
+            }
+        }
+        if (numbers.isEmpty()) {
+            // 鈥�/鈥濊〃绀烘ā鏉挎槑纭~鍐欎簡涓嶉�傜敤锛屼笉鑳藉啀鎸夌┖鍊煎鐞嗘垨杩藉姞鍗曚綅銆�
+            return candidates.stream()
+                    .filter(StringUtils::isNotBlank)
+                    .map(String::trim)
+                    .distinct()
+                    .collect(Collectors.joining("~"));
+        }
+        BigDecimal minimum = Collections.min(numbers);
+        BigDecimal maximum = Collections.max(numbers);
+        String minimumText = minimum.stripTrailingZeros().toPlainString();
+        if (minimum.compareTo(maximum) == 0) {
+            return minimumText + unit;
+        }
+        return minimumText + "~" + maximum.stripTrailingZeros().toPlainString() + unit;
+    }
+
+    /** 鎶ュ憡椤圭洰鍚嶇О涓嶅睍绀烘ā鏉胯绠楄繃绋嬩腑鐨勨�滃钩鍧囧�尖�濆瓧鏍峰強鍏跺悗鐨勫垎闅旈�楀彿銆� */
+    private static String formatExportItemName(String itemName) {
+        if (StringUtils.isBlank(itemName)) {
+            return itemName;
+        }
+        return itemName.replaceAll("骞冲潎鍊糪\s*[锛�,]?\\s*", "");
+    }
+
+    private static String getMapText(Map<String, String> map, String... keys) {
+        if (map == null) {
+            return null;
+        }
+        for (String key : keys) {
+            String value = map.get(key);
+            if (StringUtils.isNotBlank(value)) {
+                return value;
+            }
+            for (Map.Entry<String, String> entry : map.entrySet()) {
+                if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(key)
+                        && StringUtils.isNotBlank(entry.getValue())) {
+                    return entry.getValue();
+                }
+            }
+        }
+        return null;
+    }
+
+    private static String defaultReportText(String value, String fallback) {
+        if (StringUtils.isNotBlank(value)) {
+            return value;
+        }
+        return StringUtils.isNotBlank(fallback) ? fallback : "/";
     }
 
     /**
@@ -3231,6 +4152,11 @@
             }
 
         }
+        // 妫�娴嬫姤鍛婁笉灞曠ず鈥滅粨璁衡�濆垪锛岄琛屼篃瑕佸悓姝ュ噺灏戜竴涓崟鍏冩牸锛�
+        // 鍚﹀垯棣栬涓� max + 6 鍒椼�佸叾浣欒涓� max + 5 鍒楋紝poi-tl 鏃犳硶娓叉煋琛ㄦ牸銆�
+        if (!showResult && !text.isEmpty()) {
+            text.remove(text.size() - 1);
+        }
         TextRenderData[] text2 = text.toArray(new TextRenderData[0]);
         rowRenderData = Rows.of(text2).center().rowAtleastHeight(1).create();
         rows.add(rowRenderData);

--
Gitblit v1.9.3