liding
昨天 5734b7e1c2fb6e64ddc5956b84c5a125a8c1ea0e
feat(order): 增加直绑模板下单标准库绑定与设备初始化
已修改7个文件
270 ■■■■■ 文件已修改
basic-server/src/main/java/com/ruoyi/basic/utils/StandardTemplateInspectionItemParser.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrder.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderStandardTemplate.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsProductServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsSampleReceiptServiceImpl.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/utils/StandardTemplateInspectionItemParser.java
@@ -13,9 +13,10 @@
import java.util.List;
import java.util.Set;
/** 解析 Luckysheet 原始记录模板中的“检验项”标记。 */
/** 解析 Luckysheet 原始记录模板中的“检验项”;未标记时回退到“导出值”行。 */
public final class StandardTemplateInspectionItemParser {
    private static final String INSPECTION_ITEM_MARK = "检验项";
    private static final String EXPORT_VALUE_MARK = "导出值";
    private StandardTemplateInspectionItemParser() {
    }
@@ -34,11 +35,22 @@
        } catch (Exception e) {
            throw new BaseException("原始记录模板格式错误");
        }
        List<TemplateInspectionItemDto> result = new ArrayList<>();
        Set<Integer> rows = new HashSet<>();
        if (sheets == null) {
            throw new BaseException("原始记录模板格式错误");
        }
        List<TemplateInspectionItemDto> result = parseMarkedCells(sheets, templateId, templateSort,
                INSPECTION_ITEM_MARK);
        if (result.isEmpty()) {
            result = parseMarkedCells(sheets, templateId, templateSort, EXPORT_VALUE_MARK);
        }
        result.sort(Comparator.comparing(TemplateInspectionItemDto::getTemplateRowIndex));
        return result;
    }
    private static List<TemplateInspectionItemDto> parseMarkedCells(JSONArray sheets, Integer templateId,
                                                                     Integer templateSort, String mark) {
        List<TemplateInspectionItemDto> result = new ArrayList<>();
        Set<Integer> rows = new HashSet<>();
        for (Object sheetObject : sheets) {
            JSONObject sheet = (JSONObject) sheetObject;
            JSONArray cells = sheet.getJSONArray("celldata");
@@ -48,16 +60,19 @@
            for (Object cellObject : cells) {
                JSONObject cell = (JSONObject) cellObject;
                JSONObject value = cell.getJSONObject("v");
                if (value == null || !isInspectionItem(value)) {
                if (value == null || !hasMark(value, mark)) {
                    continue;
                }
                Integer row = cell.getInteger("r");
                String item = getCellText(value);
                if (row == null || !StringUtils.hasText(item)) {
                    throw new BaseException("“检验项”标记所在单元格必须填写检验项名称");
                    throw new BaseException("“" + mark + "”标记所在单元格必须填写检验项名称");
                }
                if (!rows.add(row)) {
                    throw new BaseException("同一模板行只能配置一个“检验项”标记");
                    if (EXPORT_VALUE_MARK.equals(mark)) {
                        continue;
                    }
                    throw new BaseException("同一模板行只能配置一个“" + mark + "”标记");
                }
                TemplateInspectionItemDto dto = new TemplateInspectionItemDto();
                dto.setTemplateId(templateId);
@@ -67,7 +82,6 @@
                result.add(dto);
            }
        }
        result.sort(Comparator.comparing(TemplateInspectionItemDto::getTemplateRowIndex));
        return result;
    }
@@ -101,8 +115,8 @@
        return richText.toString();
    }
    private static boolean isInspectionItem(JSONObject value) {
    private static boolean hasMark(JSONObject value, String mark) {
        JSONObject ps = value.getJSONObject("ps");
        return ps != null && INSPECTION_ITEM_MARK.equals(ps.getString("value"));
        return ps != null && mark.equals(ps.getString("value"));
    }
}
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrder.java
@@ -42,6 +42,9 @@
    @Schema(description = "质量监控计划明细ID")
    private Integer qualityMonitorDetailsId;
    @Schema(description = "新聚直绑模板下单时选择的标准库二级节点ID")
    private Integer standardTreeId;
    @Schema(description = "下单客户")
    private String custom;
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderStandardTemplate.java
@@ -64,6 +64,9 @@
    @Schema(description = "原始记录已填写单元格,按坐标保存")
    private String recordValues;
    @Schema(description = "默认设备是否已在首次进入检验任务时初始化:0否,1是")
    private Integer deviceDefaultsInitialized;
    @Schema(description = "该模板检验时的温度")
    private String temperature;
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java
@@ -1659,6 +1659,8 @@
            refreshed.setRemark(standardTemplate == null ? oldSnapshot.getRemark() : standardTemplate.getRemark());
            refreshed.setThing(standardTemplate == null ? oldSnapshot.getThing() : standardTemplate.getThing());
            refreshed.setRecordValues(templateRecordValuesMap.get(templateId));
            refreshed.setDeviceDefaultsInitialized(oldSnapshot == null ? 1
                    : oldSnapshot.getDeviceDefaultsInitialized());
            refreshed.setTemperature(templateTemperatureMap.get(templateId));
            refreshed.setHumidity(templateHumidityMap.get(templateId));
            // 刷新模板结构时必须保留检验阶段填写的业务数据,避免进入复核后回显为空。
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java
@@ -581,6 +581,9 @@
                        .thenComparing(item -> item.getTemplateRowIndex() == null
                                ? Integer.MAX_VALUE : item.getTemplateRowIndex()))
                .collect(Collectors.toList());
        if (templateItems.isEmpty()) {
            throw new ErrorException("所选原始记录模板未配置检验项或导出值,不能下单");
        }
        StandardMethod standardMethod = standardMethodService.getById(standard.getStandardMethodId());
        if (standardMethod == null || StringUtils.isBlank(standardMethod.getCode())) {
@@ -596,6 +599,7 @@
        order.setIsExemption(0);
        order.setFactory(null);
        order.setLaboratory(standard.getLaboratory());
        order.setStandardTreeId(dto.getStandardTreeId());
        order.setSampleType(null);
        order.setSample(standardCode);
        order.setPrepareUserEn(null);
@@ -740,6 +744,9 @@
                .sorted(Comparator.comparing((TemplateInspectionItemDto item) -> templateSortMap.get(item.getTemplateId()))
                        .thenComparing(item -> item.getTemplateRowIndex() == null ? Integer.MAX_VALUE : item.getTemplateRowIndex()))
                .collect(Collectors.toList());
        if (templateItems.isEmpty()) {
            throw new ErrorException("所选原始记录模板未配置检验项或导出值,不能下单");
        }
        StandardMethod standardMethod = standardMethodService.getById(standard.getStandardMethodId());
        if (standardMethod == null || StringUtils.isBlank(standardMethod.getCode())) {
            throw new ErrorException("标准编号不存在");
@@ -754,6 +761,7 @@
        order.setIsExemption(currentOrder.getIsExemption());
        order.setFactory(null);
        order.setLaboratory(standard.getLaboratory());
        order.setStandardTreeId(dto.getStandardTreeId());
        order.setSampleType(null);
        order.setSample(standardCode);
        order.setPrepareUserEn(null);
@@ -839,6 +847,7 @@
            snapshot.setRemark(template.getRemark());
            snapshot.setThing(template.getThing());
            snapshot.setSort(selection.getSort());
            snapshot.setDeviceDefaultsInitialized(0);
            insOrderStandardTemplateMapper.insert(snapshot);
            StandardTreeTemplateBindingVo binding = bindingMap.get(selection.getTemplateId());
@@ -929,7 +938,9 @@
                        .eq(StandardMethod::getCode, insOrder.getSample())
                        .select(StandardMethod::getId)).stream()
                .map(StandardMethod::getId).collect(Collectors.toList());
        if (!methodIds.isEmpty() && !orderTemplateIds.isEmpty()) {
        if (insOrder.getStandardTreeId() != null) {
            map.put("standardTreeId", insOrder.getStandardTreeId());
        } else if (!methodIds.isEmpty() && !orderTemplateIds.isEmpty()) {
            List<StandardTree> candidates = standardTreeService.list(Wrappers.<StandardTree>lambdaQuery()
                    .in(StandardTree::getStandardMethodId, methodIds)
                    .isNull(StandardTree::getSampleType)
@@ -1053,8 +1064,10 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> getInsOrderAndSample(Integer id, String laboratory) {
        Map<String, Object> map = new HashMap<>();
        initializeTemplateDevicesOnFirstOpen(id);
//        InsOrder insOrder = insOrderMapper.selectById(id);
        IfsOrderVO ifsOrderVO = insOrderMapper.selectOrderInfoById(id);
        Map<Integer, List<Integer>> templateDeviceIds = insOrderTemplateDeviceMapper.selectList(
@@ -1097,6 +1110,127 @@
        return map;
    }
    /** 首次打开检验任务时,将仍为空的模板设备从对应标准补入订单。 */
    private void initializeTemplateDevicesOnFirstOpen(Integer orderId) {
        InsOrder order = insOrderMapper.selectById(orderId);
        if (order == null || !DIRECT_TEMPLATE_LABORATORY.equals(order.getLaboratory())
                || !Objects.equals(order.getTypeSource(), -1)
                || !Objects.equals(order.getState(), 1)
                || "QUALITY_MONITOR".equals(order.getTaskSource())) {
            return;
        }
        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateMapper.selectList(
                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId)
                        .last("FOR UPDATE"));
        if (snapshots.isEmpty() || snapshots.stream().allMatch(
                snapshot -> Objects.equals(snapshot.getDeviceDefaultsInitialized(), 1))) {
            return;
        }
        Set<Integer> existingTemplateIds = insOrderTemplateDeviceMapper.selectList(
                Wrappers.<InsOrderTemplateDevice>lambdaQuery()
                        .eq(InsOrderTemplateDevice::getInsOrderId, orderId))
                .stream().map(InsOrderTemplateDevice::getTemplateId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        boolean inspectionStarted = order.getFirstInspectDate() != null
                || (order.getInsState() != null && order.getInsState() > 0)
                || snapshots.stream().anyMatch(snapshot -> StringUtils.isNotBlank(snapshot.getRecordValues())
                        || snapshot.getDetectionTime() != null || snapshot.getTemperature() != null
                        || snapshot.getDetectionPlace() != null);
        Map<Integer, List<Integer>> defaults = inspectionStarted ? Collections.emptyMap()
                : findStandardDeviceDefaults(order, snapshots);
        for (InsOrderStandardTemplate snapshot : snapshots) {
            if (Objects.equals(snapshot.getDeviceDefaultsInitialized(), 1)) {
                continue;
            }
            Integer templateId = snapshot.getTemplateId();
            if (!inspectionStarted && !existingTemplateIds.contains(templateId)
                    && defaults.containsKey(templateId)) {
                List<Integer> deviceIds = defaults.get(templateId);
                for (int index = 0; index < deviceIds.size(); index++) {
                    InsOrderTemplateDevice relation = new InsOrderTemplateDevice();
                    relation.setInsOrderId(orderId);
                    relation.setTemplateId(templateId);
                    relation.setDeviceId(deviceIds.get(index));
                    relation.setSort(index);
                    insOrderTemplateDeviceMapper.insert(relation);
                }
            }
            // 无法唯一确定来源时也只尝试这一次,避免以后标准库调整时意外改动订单。
            insOrderStandardTemplateMapper.update(null,
                    Wrappers.<InsOrderStandardTemplate>lambdaUpdate()
                            .eq(InsOrderStandardTemplate::getId, snapshot.getId())
                            .set(InsOrderStandardTemplate::getDeviceDefaultsInitialized, 1));
        }
    }
    private Map<Integer, List<Integer>> findStandardDeviceDefaults(InsOrder order,
            List<InsOrderStandardTemplate> snapshots) {
        Set<Integer> templateIds = snapshots.stream().map(InsOrderStandardTemplate::getTemplateId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        Integer standardTreeId = order.getStandardTreeId();
        if (standardTreeId == null && StringUtils.isNotBlank(order.getSample()) && !templateIds.isEmpty()) {
            List<Integer> methodIds = standardMethodService.list(Wrappers.<StandardMethod>lambdaQuery()
                            .eq(StandardMethod::getCode, order.getSample())
                            .select(StandardMethod::getId)).stream()
                    .map(StandardMethod::getId).collect(Collectors.toList());
            if (!methodIds.isEmpty()) {
                List<StandardTree> candidates = standardTreeService.list(Wrappers.<StandardTree>lambdaQuery()
                        .in(StandardTree::getStandardMethodId, methodIds)
                        .eq(StandardTree::getLaboratory, order.getLaboratory())
                        .isNotNull(StandardTree::getSample)
                        .isNull(StandardTree::getSampleType)
                        .isNull(StandardTree::getModel));
                List<StandardTree> matches = candidates.stream()
                        .filter(tree -> StringUtils.isNotBlank(tree.getSample()))
                        .filter(tree -> {
                            Set<Integer> boundIds = standardTreeService.selectTemplateBindings(tree.getId()).stream()
                                    .map(StandardTreeTemplateBindingVo::getTemplateId).collect(Collectors.toSet());
                            return boundIds.containsAll(templateIds);
                        }).collect(Collectors.toList());
                if (matches.size() == 1) {
                    standardTreeId = matches.get(0).getId();
                } else {
                    List<StandardTree> exact = matches.stream().filter(tree ->
                            standardTreeService.selectTemplateBindings(tree.getId()).stream()
                                    .map(StandardTreeTemplateBindingVo::getTemplateId)
                                    .collect(Collectors.toSet()).equals(templateIds))
                            .collect(Collectors.toList());
                    if (exact.size() == 1) {
                        standardTreeId = exact.get(0).getId();
                    }
                }
            }
        }
        if (standardTreeId == null) {
            return Collections.emptyMap();
        }
        StandardTree standard = standardTreeService.getById(standardTreeId);
        if (standard == null || StringUtils.isBlank(standard.getSample())
                || !Objects.equals(standard.getLaboratory(), order.getLaboratory())
                || standard.getSampleType() != null || standard.getModel() != null) {
            return Collections.emptyMap();
        }
        if (order.getStandardTreeId() == null) {
            insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                    .eq(InsOrder::getId, order.getId())
                    .isNull(InsOrder::getStandardTreeId)
                    .set(InsOrder::getStandardTreeId, standardTreeId));
        }
        List<StandardTreeTemplateBindingVo> bindings = standardTreeService.selectTemplateBindings(standardTreeId);
        Map<Integer, List<Integer>> defaults = new HashMap<>();
        for (StandardTreeTemplateBindingVo binding : bindings) {
            if (templateIds.contains(binding.getTemplateId())) {
                defaults.put(binding.getTemplateId(), binding.getDeviceIds() == null
                        ? Collections.emptyList() : binding.getDeviceIds());
            }
        }
        return defaults;
    }
    @Override
    public IPage<SampleProductDto2> selectSampleAndProductByOrderId(IPage<SampleProductDto2> page, SampleProductDto2 sampleProductDto) {
        IPage<SampleProductDto2> productDto2IPage = insOrderMapper.selectSampleAndProductByOrderId(page,
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsProductServiceImpl.java
@@ -94,6 +94,11 @@
                                        InsOrderStandardTemplate::getInsResult, dto.getInsResult()));
            }
            if (dto.isDeviceIdsPresent()) {
                // 手动选择或清空都视为已初始化,后续进入任务不得再次套用标准库默认值。
                insOrderStandardTemplateMapper.update(null,
                        Wrappers.<InsOrderStandardTemplate>lambdaUpdate()
                                .eq(InsOrderStandardTemplate::getId, snapshot.getId())
                                .set(InsOrderStandardTemplate::getDeviceDefaultsInitialized, 1));
                insOrderTemplateDeviceMapper.delete(
                        Wrappers.<InsOrderTemplateDevice>lambdaQuery()
                                .eq(InsOrderTemplateDevice::getInsOrderId, dto.getId())
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsSampleReceiptServiceImpl.java
@@ -4,46 +4,44 @@
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.TemplateInspectionItemDto;
import com.ruoyi.basic.utils.StandardTemplateInspectionItemParser;
import com.ruoyi.common.utils.GZipUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.inspect.dto.SampleReceiptActionDto;
import com.ruoyi.inspect.dto.SampleReceiptPageDto;
import com.ruoyi.inspect.dto.SampleReceiptQueryDto;
import com.ruoyi.inspect.mapper.InsOrderMapper;
import com.ruoyi.inspect.mapper.InsOrderStandardTemplateMapper;
import com.ruoyi.inspect.mapper.InsProductMapper;
import com.ruoyi.inspect.mapper.InsSampleMapper;
import com.ruoyi.inspect.mapper.InsSampleReceiptMapper;
import com.ruoyi.inspect.mapper.InsSampleUserMapper;
import com.ruoyi.inspect.pojo.InsOrder;
import com.ruoyi.inspect.pojo.InsOrderStandardTemplate;
import com.ruoyi.inspect.pojo.InsProduct;
import com.ruoyi.inspect.pojo.InsSample;
import com.ruoyi.inspect.pojo.InsSampleReceipt;
import com.ruoyi.inspect.pojo.InsSampleUser;
import com.ruoyi.inspect.mapper.*;
import com.ruoyi.inspect.pojo.*;
import com.ruoyi.inspect.service.InsOrderService;
import com.ruoyi.inspect.service.InsOrderStateService;
import com.ruoyi.inspect.service.InsSampleReceiptService;
import com.ruoyi.system.mapper.UserMapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
public class InsSampleReceiptServiceImpl extends ServiceImpl<InsSampleReceiptMapper, InsSampleReceipt>
        implements InsSampleReceiptService {
    private static final String DIRECT_TEMPLATE_LABORATORY = "新聚";
    private final InsSampleReceiptMapper receiptMapper;
    private final InsOrderMapper insOrderMapper;
    private final InsOrderStandardTemplateMapper insOrderStandardTemplateMapper;
@@ -55,13 +53,17 @@
    private final UserMapper userMapper;
    @Override
    public com.baomidou.mybatisplus.core.metadata.IPage<SampleReceiptPageDto> page(Page<?> page, SampleReceiptQueryDto query) {
        return receiptMapper.selectPage(page, query);
    public IPage<SampleReceiptPageDto> page(Page<?> page, SampleReceiptQueryDto query) {
        IPage<SampleReceiptPageDto> result = receiptMapper.selectPage(page, query);
        result.getRecords().forEach(this::fillInspectionItemsFromSnapshots);
        return result;
    }
    @Override
    public SampleReceiptPageDto detail(Integer id) {
        return receiptMapper.selectDetail(id);
        SampleReceiptPageDto detail = receiptMapper.selectDetail(id);
        fillInspectionItemsFromSnapshots(detail);
        return detail;
    }
    @Override
@@ -91,7 +93,26 @@
        Long productCount = insProductMapper.selectCount(Wrappers.<InsProduct>lambdaQuery()
                .eq(InsProduct::getInsSampleId, sample.getId()).eq(InsProduct::getState, 1));
        if (productCount == 0) {
            throw new ErrorException("该委托未绑定检验项目,不能提交检验");
            List<TemplateInspectionItemDto> items = getSnapshotInspectionItems(order.getId());
            if (items.isEmpty()) {
                throw new ErrorException("订单原始记录模板未配置检验项或导出值,不能提交检验");
            }
            int sort = 0;
            for (TemplateInspectionItemDto item : items) {
                InsProduct product = new InsProduct();
                product.setInsSampleId(sample.getId());
                product.setInspectionItem(item.getInspectionItem());
                product.setInspectionItemClass("");
                product.setInspectionItemSubclass("");
                product.setLaboratory(order.getLaboratory());
                product.setSample(order.getSample());
                product.setTemplateId(item.getTemplateId());
                product.setTemplateRowIndex(item.getTemplateRowIndex());
                product.setSonLaboratory(DIRECT_TEMPLATE_LABORATORY);
                product.setState(1);
                product.setSort(sort++);
                insProductMapper.insert(product);
            }
        }
        if (userMapper.selectById(dto.getMainInspectorId()) == null) {
            throw new ErrorException("主检验人不存在或已停用");
@@ -160,9 +181,43 @@
        }
    }
    private List<TemplateInspectionItemDto> getSnapshotInspectionItems(Integer orderId) {
        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateMapper.selectList(
                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
        List<TemplateInspectionItemDto> items = new ArrayList<>();
        for (InsOrderStandardTemplate snapshot : snapshots) {
            String thing = snapshot.getThing();
            try {
                thing = GZipUtil.uncompress(thing);
            } catch (RuntimeException ignored) {
                // 兼容未压缩的历史模板快照。
            }
            items.addAll(StandardTemplateInspectionItemParser.parse(
                    snapshot.getTemplateId(), snapshot.getSort(), thing));
        }
        return items;
    }
    private void fillInspectionItemsFromSnapshots(SampleReceiptPageDto row) {
        if (row == null || row.getOrderId() == null || StringUtils.isNotBlank(row.getInspectionItem())) {
            return;
        }
        try {
            String names = getSnapshotInspectionItems(row.getOrderId()).stream()
                    .map(TemplateInspectionItemDto::getInspectionItem)
                    .distinct().collect(Collectors.joining("\n"));
            row.setInspectionItem(names);
        } catch (RuntimeException ignored) {
            // 列表仍可查看;提交检验时会返回具体的模板解析错误。
        }
    }
    @Override
    public void export(SampleReceiptQueryDto query, HttpServletResponse response) {
        List<SampleReceiptPageDto> list = receiptMapper.selectExportList(query);
        list.forEach(this::fillInspectionItemsFromSnapshots);
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("UTF-8");
        try {