inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java
@@ -84,6 +84,8 @@
public class InsOrderServiceImpl extends ServiceImpl<InsOrderMapper, InsOrder>
        implements InsOrderService {
    private static final String DIRECT_TEMPLATE_LABORATORY = "新聚";
    private InsOrderMapper insOrderMapper;
    private InsSampleService insSampleService;
    private InsSampleMapper insSampleMapper;
@@ -137,36 +139,52 @@
        insOrderMapper.updateById(insOrder);
        List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery().eq(InsSample::getInsOrderId, orderId));
        List<Integer> ids = insSamples.stream().map(InsSample::getId).collect(Collectors.toList());
        List<InsProduct> insProducts = insProductMapper.selectList(Wrappers.<InsProduct>lambdaQuery()
                .in(InsProduct::getInsSampleId, ids)
                .eq(InsProduct::getState, 1)
                .select(InsProduct::getSonLaboratory).groupBy(InsProduct::getSonLaboratory));
        List<InsProduct> insProducts = ids.isEmpty()
                ? Collections.emptyList()
                : insProductMapper.selectList(Wrappers.<InsProduct>lambdaQuery()
                        .in(InsProduct::getInsSampleId, ids)
                        .eq(InsProduct::getState, 1)
                        .select(InsProduct::getSonLaboratory).groupBy(InsProduct::getSonLaboratory));
        InsOrder order = insOrderMapper.selectById(orderId);
        boolean directTemplateOrder = isDirectTemplateOrder(order, insSamples);
        Set<String> taskLaboratories = directTemplateOrder
                ? Collections.singleton(DIRECT_TEMPLATE_LABORATORY)
                : insProducts.stream()
                        .map(InsProduct::getSonLaboratory)
                        .filter(StringUtils::isNotBlank)
                        .collect(Collectors.toCollection(LinkedHashSet::new));
        if (!directTemplateOrder && taskLaboratories.size() != insProducts.size()) {
            throw new ErrorException("该检验单有未维护实验室的检验项目");
        }
        // 批量添加检验任务状态表
        List<InsOrderState> insOrderStateList = insProducts.stream().map(insProduct -> {
        List<InsOrderState> insOrderStateList = taskLaboratories.stream()
                .filter(laboratory -> insOrderStateService.count(Wrappers.<InsOrderState>lambdaQuery()
                        .eq(InsOrderState::getInsOrderId, orderId)
                        .eq(InsOrderState::getLaboratory, laboratory)) == 0)
                .map(laboratory -> {
            InsOrderState insOrderState = new InsOrderState();
            insOrderState.setInsOrderId(orderId);
            try {
                insOrderState.setLaboratory(insProduct.getSonLaboratory());
            } catch (NullPointerException e) {
                throw new ErrorException("该检验单有未维护实验室的检验项目");
            }
            insOrderState.setLaboratory(laboratory);
            insOrderState.setInsState(0);
            return insOrderState;
        }).collect(Collectors.toList());
        insOrderStateService.saveBatch(insOrderStateList);
        if (!insOrderStateList.isEmpty()) {
            insOrderStateService.saveBatch(insOrderStateList);
        }
        if (userId != null) {
            InsSampleUser insSampleUser = new InsSampleUser();
            insSampleUser.setState(0);
            insSampleUser.setUserId(userId);
            insSampleUser.setInsSampleId(orderId);
            insSampleUser.setSonLaboratory(sonLaboratory);
            insSampleUser.setSonLaboratory(directTemplateOrder
                    ? DIRECT_TEMPLATE_LABORATORY : sonLaboratory);
            insSampleUserMapper.insert(insSampleUser);
        }
        // 判断订单有没有绑定抽样计划
        InsOrder order = insOrderMapper.selectById(orderId);
        if (order.getQuarterItemId() != null) {
           // 需要添加下发时间到抽样时间, 取样人员就是检测人
            SpotCheckQuarterItem spotCheckQuarterItem = spotCheckQuarterItemMapper.selectById(order.getQuarterItemId());
@@ -177,6 +195,22 @@
        }
        return 1;
    }
    private boolean isDirectTemplateOrder(InsOrder order, List<InsSample> samples) {
        if (order == null || CollectionUtils.isEmpty(samples)) {
            return false;
        }
        Long snapshotCount = insOrderStandardTemplateMapper.selectCount(
                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                        .eq(InsOrderStandardTemplate::getInsOrderId, order.getId()));
        return snapshotCount > 0 && samples.stream().anyMatch(sample ->
                sample.getStandardMethodListId() == null
                        && StringUtils.isBlank(sample.getFactory())
                        && StringUtils.isBlank(sample.getSampleType())
                        && StringUtils.isBlank(sample.getModel())
                        && StringUtils.isBlank(order.getFactory())
                        && StringUtils.isBlank(order.getSampleType()));
    }
@@ -330,6 +364,15 @@
                }
            }
        });
        // 下单时生成委托编号并校验唯一性;季度检验复用进厂检验编号,留待审核处理。
        if (!InsOrderTypeConstants.QUARTERLY_TEST.equals(insOrder.getOrderType())) {
            String entrustCode = buildEntrustCode(insOrder, Collections.emptyList());
            assertEntrustCodeUnique(entrustCode, insOrder.getId());
            insOrder.setEntrustCode(entrustCode);
            insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                    .eq(InsOrder::getId, insOrder.getId())
                    .set(InsOrder::getEntrustCode, entrustCode));
        }
        //原材料下单或外购成品下单
        if (Objects.nonNull(insOrder.getTypeSource())&&Objects.nonNull(insOrder.getIfsInventoryId())) {
            // 委托人就是报检人, 生产单位就是供应商单位
@@ -430,6 +473,10 @@
        if (dto == null || dto.getInsOrder() == null || dto.getStandardTreeId() == null) {
            throw new ErrorException("下单信息或标准不能为空");
        }
        String sampleCode = StringUtils.trim(dto.getSampleCode());
        if (StringUtils.isBlank(sampleCode) || sampleCode.length() < 2) {
            throw new ErrorException("样品编号不能为空且不能少于2位");
        }
        StandardTree standard = standardTreeService.getById(dto.getStandardTreeId());
        if (standard == null || StringUtils.isBlank(standard.getSample())
                || standard.getSampleType() != null || standard.getModel() != null) {
@@ -506,7 +553,7 @@
        InsSample sample = new InsSample();
        sample.setInsOrderId(order.getId());
        sample.setSampleCode(dto.getSampleCode());
        sample.setSampleCode(sampleCode);
        sample.setFactory(null);
        sample.setLaboratory(standard.getLaboratory());
        sample.setSampleType(null);
@@ -530,16 +577,24 @@
            product.setModel(null);
            product.setTemplateId(item.getTemplateId());
            product.setTemplateRowIndex(item.getTemplateRowIndex());
            product.setSonLaboratory("新聚");
            product.setSonLaboratory(DIRECT_TEMPLATE_LABORATORY);
            product.setState(1);
            product.setSort(productSort++);
            insProductMapper.insert(product);
        }
        saveTemplateSnapshots(order.getId(), selections);
        saveTemplateSnapshots(order.getId(), selections, bindingMap);
        // 下单时生成委托编号并校验唯一性,重复则提示,避免留到审核才报错。
        String entrustCode = buildEntrustCode(order, Collections.singletonList(sample));
        assertEntrustCodeUnique(entrustCode, order.getId());
        insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                .eq(InsOrder::getId, order.getId())
                .set(InsOrder::getEntrustCode, entrustCode));
        return order.getId();
    }
    private void saveTemplateSnapshots(Integer orderId, List<OrderTemplateSelectionDto> selections) {
    private void saveTemplateSnapshots(Integer orderId, List<OrderTemplateSelectionDto> selections,
                                       Map<Integer, StandardTreeTemplateBindingVo> bindingMap) {
        for (OrderTemplateSelectionDto selection : selections) {
            StandardTemplate template = standardTemplateService.getById(selection.getTemplateId());
            if (template == null) {
@@ -554,6 +609,18 @@
            snapshot.setThing(template.getThing());
            snapshot.setSort(selection.getSort());
            insOrderStandardTemplateMapper.insert(snapshot);
            StandardTreeTemplateBindingVo binding = bindingMap.get(selection.getTemplateId());
            List<Integer> deviceIds = binding == null || binding.getDeviceIds() == null
                    ? Collections.emptyList() : binding.getDeviceIds();
            for (int index = 0; index < deviceIds.size(); index++) {
                InsOrderTemplateDevice relation = new InsOrderTemplateDevice();
                relation.setInsOrderId(orderId);
                relation.setTemplateId(selection.getTemplateId());
                relation.setDeviceId(deviceIds.get(index));
                relation.setSort(index);
                insOrderTemplateDeviceMapper.insert(relation);
            }
        }
    }
@@ -637,17 +704,12 @@
    public int upInsOrderOfState(InsOrder insOrder) {
        insOrder.setExamineTime(LocalDateTime.now());
        if (insOrder.getState() == 1) {
            //审核通过才会生成委托编号
            //委托编号已在下单时生成并校验唯一性,审核时复用;历史/季度检验订单兜底生成。
            InsOrder order = this.getById(insOrder.getId());
            // 委托编号按送样日期独立流水,例如:LMSY-20260909-01。
            // 历史/外部订单未填写送样日期时回退到当天,避免影响原有流程。
            LocalDate entrustDate = order.getSampleDate() == null ? LocalDate.now() : order.getSampleDate();
            String entrustCodePrefix = "LMSY-"
                    + entrustDate.format(DateTimeFormatter.BASIC_ISO_DATE)
                    + "-";
            String no = numberGenerator.generateNumberWithPrefix(2,
                    entrustCodePrefix,
                    InsOrder::getEntrustCode);
            List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery()
                    .eq(InsSample::getInsOrderId, insOrder.getId())
                    .orderByAsc(InsSample::getId));
            String no = order.getEntrustCode();
            // 判断是否是季度检验, 是季度检验取消原材料季度检验下单
            if (InsOrderTypeConstants.QUARTERLY_TEST.equals(order.getOrderType())) {
                ifsInventoryQuantityMapper.update(null,  Wrappers.<IfsInventoryQuantity>lambdaUpdate()
@@ -663,11 +725,12 @@
                  no = order1.getEntrustCode();
                }
            }
            if (StringUtils.isBlank(no)) {
                no = buildEntrustCode(order, insSamples);
            }
            assertEntrustCodeUnique(no, order.getId());
            int count = 1;
            // 查询样品表
            List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery()
                    .eq(InsSample::getInsOrderId, insOrder.getId()));
            for (InsSample insSample : insSamples) {
                if (StringUtils.isBlank(insSample.getSampleCode())) {
                    // 如果只有一个样品就不需要拼接数字
@@ -686,6 +749,37 @@
        return insOrderMapper.updateById(insOrder);
    }
    /**
     * 委托编号全局唯一,生成编号和后续提交前均需排除当前订单后校验。
     */
    private void assertEntrustCodeUnique(String entrustCode, Integer currentOrderId) {
        Long count = insOrderMapper.selectCount(Wrappers.<InsOrder>lambdaQuery()
                .eq(InsOrder::getEntrustCode, entrustCode)
                .ne(currentOrderId != null, InsOrder::getId, currentOrderId));
        if (count > 0) {
            throw new ErrorException("样品/委托单已存在,请确认样品编号");
        }
    }
    /**
     * 生成委托编号:新聚直绑模板订单末两位取样品编号末两位,其余按送样日期生成序号。
     */
    private String buildEntrustCode(InsOrder order, List<InsSample> insSamples) {
        LocalDate entrustDate = order.getSampleDate() == null ? LocalDate.now() : order.getSampleDate();
        String entrustCodePrefix = "LMSY-"
                + entrustDate.format(DateTimeFormatter.BASIC_ISO_DATE)
                + "-";
        if (isDirectTemplateOrder(order, insSamples)) {
            String sampleCode = insSamples.get(0).getSampleCode();
            if (StringUtils.isBlank(sampleCode) || sampleCode.trim().length() < 2) {
                throw new ErrorException("样品编号不能为空且不能少于2位");
            }
            sampleCode = sampleCode.trim();
            return entrustCodePrefix + sampleCode.substring(sampleCode.length() - 2);
        }
        return numberGenerator.generateNumberWithPrefix(2, entrustCodePrefix, InsOrder::getEntrustCode);
    }
    @Override
    public Map<String, Object> getInsOrderAndSample(Integer id, String laboratory) {
        Map<String, Object> map = new HashMap<>();