feat: 1委托单 2下单详情 3检验任务 4检验任务复核 5检验任务详情 6标准维护 7检验单详情 8报告编制
已添加26个文件
已修改37个文件
3714 ■■■■ 文件已修改
basic-server/src/main/java/com/ruoyi/basic/controller/StandardMethodController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/controller/StandardTreeController.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/AddStandardTreeNodeDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/StandardLibraryTreeNodeDto.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/StandardLibraryTreeRowDto.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/StandardTreeTemplateBindingDto.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/StandardTreeTemplateBindingVo.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/TemplateBindingItemDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/TemplateInspectionItemDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/UpdateStandardTreeNodeDto.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardMethodAttachmentMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardMethodIndustryMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardMethodMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardTreeMapper.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardTreeTemplateBindingMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/pojo/Laboratory.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardMethod.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardMethodAttachment.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardMethodIndustry.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardTree.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardTreeTemplateBinding.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/StandardMethodService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/StandardProductListService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/StandardTreeService.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardMethodServiceImpl.java 251 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardProductListServiceImpl.java 388 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardTemplateServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardTreeServiceImpl.java 470 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/utils/StandardTemplateInspectionItemParser.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/utils/StandardTreePath.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/utils/StandardTreePathParser.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/resources/mapper/StandardMethodMapper.xml 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/resources/mapper/StandardTreeMapper.xml 156 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-require/src/main/java/com/ruoyi/require/service/impl/FeStandardSubstanceServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsOrderController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsOrderPlanController.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsReportController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/InsPlaceOrderDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/OrderTemplateSelectionDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/SaveOrderDevicesDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/SaveTemplateContextDto.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/VerifyPlanDto.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/WriteInspectionConditionDto.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderTemplateDeviceMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsReportMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrder.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderStandardTemplate.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderTemplateDevice.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsProduct.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderPlanService.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsProductService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsReportService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java 1032 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsProductServiceImpl.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsReportServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/IfsOrderVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/OrderTemplateSummaryVo.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/TemplateConditionVo.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsOrderMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsReportMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsSampleMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/controller/StandardMethodController.java
@@ -50,4 +50,29 @@
        return Result.success(standardMethodService.upStandardMethod(standardMethod));
    }
    @ApiOperation(value = "获取行业选项")
    @GetMapping("/selectIndustryOptions")
    public Result selectIndustryOptions() {
        return Result.success(standardMethodService.selectIndustryOptions());
    }
    @ApiOperation(value = "查询标准附件")
    @GetMapping("/selectAttachments")
    public Result selectAttachments(Integer standardMethodId) {
        return Result.success(standardMethodService.selectAttachments(standardMethodId));
    }
    @ApiOperation(value = "上传标准附件")
    @PostMapping("/uploadAttachment")
    public Result uploadAttachment(@RequestParam("standardMethodId") Integer standardMethodId,
                                   @RequestParam("file") MultipartFile file) {
        return Result.success(standardMethodService.uploadAttachment(standardMethodId, file));
    }
    @ApiOperation(value = "删除标准附件")
    @DeleteMapping("/deleteAttachment")
    public Result deleteAttachment(Long id) {
        return Result.success(standardMethodService.deleteAttachment(id));
    }
}
basic-server/src/main/java/com/ruoyi/basic/controller/StandardTreeController.java
@@ -4,13 +4,15 @@
import com.alibaba.fastjson.JSONArray;
import com.ruoyi.basic.service.*;
import com.ruoyi.common.core.domain.Result;
import com.ruoyi.basic.dto.AddStandardTreeNodeDto;
import com.ruoyi.basic.dto.CopyStandardProductListDto;
import com.ruoyi.basic.dto.FactoryDto;
import com.ruoyi.basic.dto.InsSampleReceiveDto;
import com.ruoyi.basic.dto.ResetTreeDragDTO;
import com.ruoyi.basic.dto.StandardLibraryTreeNodeDto;
import com.ruoyi.basic.dto.UpdateStandardTreeNodeDto;
import com.ruoyi.basic.dto.StandardTreeTemplateBindingDto;
import com.ruoyi.basic.pojo.StandardProductList;
import com.ruoyi.basic.pojo.StandardProductListSupplierAsk;
import com.ruoyi.basic.pojo.StandardTree;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -50,8 +52,8 @@
    @ApiOperation(value = "添加标准树")
    @PostMapping("/addStandardTree")
    public Result addStandardTree(@RequestBody StandardTree standardTree) {
        return Result.success(standardTreeService.addStandardTree(standardTree));
    public Result addStandardTree(@RequestBody AddStandardTreeNodeDto dto) {
        return Result.success(standardTreeService.addStandardTree(dto));
    }
    @ApiOperation(value = "根据标准树进行标准查询")
@@ -93,8 +95,8 @@
    @ApiOperation(value = "通过检验标准查询检验项目")
    @GetMapping("/selectStandardProductListByMethodId")
    public Result selectStandardProductListByMethodId(Integer id, String tree) {
        return Result.success(standardProductListService.selectStandardProductListByMethodId(id, tree));
    public Result selectStandardProductListByMethodId(Integer id, String tree, Integer page, String keyword) {
        return Result.success(standardProductListService.selectStandardProductListByMethodId(id, tree, page, keyword));
    }
    @ApiOperation(value = "批量查询检验项目")
@@ -149,7 +151,7 @@
    @ApiOperation(value = "标准树排序")
    @PostMapping("/updateTreeSort")
    public Result updateTreeSort(@RequestBody List<FactoryDto> list) {
    public Result updateTreeSort(@RequestBody List<StandardLibraryTreeNodeDto> list) {
        return Result.success(standardTreeService.updateTreeSort(list));
    }
@@ -187,8 +189,20 @@
    @ApiOperation(value = "修改标准树")
    @PostMapping("/updateStandardTree")
    public Result updateStandardTree(@RequestBody StandardTree standardTree) {
        return Result.success(standardTreeService.updateStandardTree(standardTree));
    public Result updateStandardTree(@RequestBody UpdateStandardTreeNodeDto dto) {
        return Result.success(standardTreeService.updateStandardTree(dto));
    }
    @ApiOperation(value = "查询标准绑定的原始记录模板")
    @GetMapping("/selectTemplateBindings")
    public Result selectTemplateBindings(Integer standardTreeId) {
        return Result.success(standardTreeService.selectTemplateBindings(standardTreeId));
    }
    @ApiOperation(value = "保存标准绑定的原始记录模板")
    @PostMapping("/saveTemplateBindings")
    public Result saveTemplateBindings(@RequestBody StandardTreeTemplateBindingDto dto) {
        return Result.success(standardTreeService.saveTemplateBindings(dto));
    }
    @ApiOperation(value = "检验项要求值复制对比")
basic-server/src/main/java/com/ruoyi/basic/dto/AddStandardTreeNodeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.ruoyi.basic.dto;
import lombok.Data;
/**
 * æ–°å¢žæ ‡å‡†æ ‘节点请求
 */
@Data
public class AddStandardTreeNodeDto {
    /**
     * çˆ¶èŠ‚ç‚¹è·¯å¾„ï¼šä¸ºç©ºè¡¨ç¤ºæ–°å¢žè¡Œä¸šï¼›ä¸ºå•ä¸ªè¡Œä¸šåç§°è¡¨ç¤ºåœ¨è¯¥è¡Œä¸šä¸‹æ–°å¢žæ ‡å‡†
     */
    private String parentTree;
    private String name;
}
basic-server/src/main/java/com/ruoyi/basic/dto/StandardLibraryTreeNodeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.ruoyi.basic.dto;
import lombok.Data;
import java.util.List;
/**
 * æ ‡å‡†åº“固定两级树节点(行业 -> æ ‡å‡†ï¼‰
 */
@Data
public class StandardLibraryTreeNodeDto {
    private Integer id;
    private String label;
    private String value;
    private String code;
    private String sampleEn;
    private Integer standardMethodId;
    private String standardCode;
    private String standardName;
    private List<StandardLibraryTreeNodeDto> children;
}
basic-server/src/main/java/com/ruoyi/basic/dto/StandardLibraryTreeRowDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.ruoyi.basic.dto;
import lombok.Data;
/**
 * æ ‡å‡†åº“两级树查询的扁平行结果(行业 + æ ‡å‡†ï¼‰
 */
@Data
public class StandardLibraryTreeRowDto {
    private Integer industryId;
    private String industryName;
    private Integer industrySort;
    private Integer standardId;
    private Integer standardMethodId;
    private String standardCode;
    private String standardName;
    private String standardNameEn;
    private String standardLabel;
    private Integer standardSort;
}
basic-server/src/main/java/com/ruoyi/basic/dto/StandardTreeTemplateBindingDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package com.ruoyi.basic.dto;
import lombok.Data;
import java.util.List;
@Data
public class StandardTreeTemplateBindingDto {
    private Integer standardTreeId;
    private List<TemplateBindingItemDto> templates;
}
basic-server/src/main/java/com/ruoyi/basic/dto/StandardTreeTemplateBindingVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.basic.dto;
import lombok.Data;
@Data
public class StandardTreeTemplateBindingVo {
    private Integer templateId;
    private String name;
    private String number;
    private String remark;
    private Integer sort;
}
basic-server/src/main/java/com/ruoyi/basic/dto/TemplateBindingItemDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
package com.ruoyi.basic.dto;
import lombok.Data;
@Data
public class TemplateBindingItemDto {
    private Integer templateId;
    private Integer sort;
}
basic-server/src/main/java/com/ruoyi/basic/dto/TemplateInspectionItemDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.basic.dto;
import lombok.Data;
/** åŽŸå§‹è®°å½•æ¨¡æ¿ä¸­æ ‡è®°ä¸ºâ€œæ£€éªŒé¡¹â€çš„ä¸€è¡Œã€‚ */
@Data
public class TemplateInspectionItemDto {
    private Integer templateId;
    private Integer templateSort;
    private Integer templateRowIndex;
    private String inspectionItem;
}
basic-server/src/main/java/com/ruoyi/basic/dto/UpdateStandardTreeNodeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.basic.dto;
import lombok.Data;
/**
 * ä¿®æ”¹æ ‡å‡†æ ‘节点请求
 */
@Data
public class UpdateStandardTreeNodeDto {
    /**
     * çˆ¶èŠ‚ç‚¹è·¯å¾„ï¼šä¸ºç©ºè¡¨ç¤ºä¿®æ”¹è¡Œä¸šï¼›ä¸ºè¡Œä¸šåç§°è¡¨ç¤ºä¿®æ”¹è¯¥è¡Œä¸šä¸‹çš„æ ‡å‡†
     */
    private String parentTree;
    private String oldLabel;
    private String name;
}
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardMethodAttachmentMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.StandardMethodAttachment;
public interface StandardMethodAttachmentMapper extends BaseMapper<StandardMethodAttachment> {
}
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardMethodIndustryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.StandardMethodIndustry;
public interface StandardMethodIndustryMapper extends BaseMapper<StandardMethodIndustry> {
}
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardMethodMapper.java
@@ -29,6 +29,8 @@
    List<StandardMethodList> selectStandardMethodLists3(@Param("tree") String tree);
    List<StandardMethodList> selectStandardMethodListsByIndustry(@Param("industry") String industry);
    List<StandardMethodList> selectListEnum();
    Integer getStandardMethodId(@Param("code") String code);
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardTreeMapper.java
@@ -24,9 +24,37 @@
@Mapper
public interface StandardTreeMapper extends BaseMapper<StandardTree> {
    List<FactoryDto> selectStandardTreeList();
    List<StandardLibraryTreeRowDto> selectStandardLibraryTree();
    List<StandardTree> selectStandardTreeList2(String sampleType);
    /**
     * è¡Œä¸šæ”¹åï¼šçº§è”更新标准库配置中的行业名称,并重建两级规范路径
     */
    int updateProductListIndustry(@Param("oldName") String oldName, @Param("newName") String newName);
    /**
     * æ ‡å‡†æ”¹åï¼šçº§è”更新标准库配置中的标准名称,并重建两级规范路径
     */
    int updateProductListStandard(@Param("industry") String industry, @Param("oldName") String oldName, @Param("newName") String newName);
    /**
     * è¡Œä¸šæ”¹åï¼šçº§è”更新未完成订单的行业名称
     */
    int updateInsOrderIndustry(@Param("oldName") String oldName, @Param("newName") String newName);
    int updateInsSampleIndustry(@Param("oldName") String oldName, @Param("newName") String newName);
    int updateInsProductIndustry(@Param("oldName") String oldName, @Param("newName") String newName);
    /**
     * æ ‡å‡†æ”¹åï¼šçº§è”更新未完成订单的标准名称
     */
    int updateInsOrderStandard(@Param("industry") String industry, @Param("oldName") String oldName, @Param("newName") String newName);
    int updateInsSampleStandard(@Param("industry") String industry, @Param("oldName") String oldName, @Param("newName") String newName);
    int updateInsProductStandard(@Param("industry") String industry, @Param("oldName") String oldName, @Param("newName") String newName);
    StandardProductList selectStandardProductById(Integer id);
@@ -36,6 +64,11 @@
    List<StandardProductList> selectStandardProductListByTree2(@Param("tree") String tree, @Param("sample") String sample, @Param("model") String model, @Param("trees") String trees, @Param("laboratory") String laboratory);
    /**
     * æŒ‰è¡Œä¸šæŸ¥è¯¢åŸºç¡€æ£€éªŒé¡¹ï¼Œç”Ÿæˆä¸¤çº§æ ‡å‡†é…ç½®ï¼ˆsample=标准、model=NULL、tree=规范路径)
     */
    List<StandardProductList> selectStandardProductListByIndustry(@Param("industry") String industry, @Param("standard") String standard, @Param("tree") String tree);
    List<SampleTypeDto> getStandardTree2();
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardTreeTemplateBindingMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.StandardTreeTemplateBinding;
/** æ ‡å‡†æ¨¡æ¿ç»‘定 Mapper。 */
public interface StandardTreeTemplateBindingMapper extends BaseMapper<StandardTreeTemplateBinding> {
}
basic-server/src/main/java/com/ruoyi/basic/pojo/Laboratory.java
@@ -39,6 +39,9 @@
    @ApiModelProperty(value = "地址")
    private String address;
    @ApiModelProperty(value = "行业排序")
    private Integer sort;
    @ApiModelProperty(value = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardMethod.java
@@ -7,6 +7,7 @@
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
 * æ ‡å‡†æ–¹æ³•
@@ -26,6 +27,18 @@
    @ApiModelProperty("检验对象")
    private String structureTestObjectId;
    /** æ ‡å‡†åº“一级行业ID(仅接口交互,不落standard_method表) */
    @TableField(exist = false)
    private List<Integer> industryIds;
    /** å·²ç»‘定行业名称(列表展示) */
    @TableField(exist = false)
    private String industryNames;
    /** é™„件数量(列表展示) */
    @TableField(exist = false)
    private Integer attachmentCount;
    /**
     * æ ‡å‡†ç¼–号
     */
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardMethodAttachment.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("standard_method_attachment")
public class StandardMethodAttachment {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Integer standardMethodId;
    private String fileName;
    private String fileUrl;
    private Integer sort;
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardMethodIndustry.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("standard_method_industry")
public class StandardMethodIndustry {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Integer standardMethodId;
    private Integer industryId;
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardTree.java
@@ -62,6 +62,14 @@
    @Length(max= 255,message="编码长度不能超过255")
    private String model;
    /**
    * æ ‡å‡†æŽ’序
    */
    @ApiModelProperty("标准排序")
    private Integer sort;
    /** æ ‡å‡†ç»´æŠ¤ID;规范化二级标准使用 */
    private Integer standardMethodId;
    /**
    *
    */
    @ApiModelProperty("")
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardTreeTemplateBinding.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/** æ ‡å‡†ï¼ˆäºŒçº§èŠ‚ç‚¹ï¼‰ä¸ŽåŽŸå§‹è®°å½•æ¨¡æ¿çš„ç»‘å®šå…³ç³»ã€‚ */
@Data
@TableName("standard_tree_template_binding")
public class StandardTreeTemplateBinding {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer standardTreeId;
    private Integer templateId;
    private Integer sort;
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
basic-server/src/main/java/com/ruoyi/basic/service/StandardMethodService.java
@@ -5,6 +5,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.pojo.StandardMethod;
import com.ruoyi.basic.pojo.StandardMethodList;
import com.ruoyi.basic.pojo.StandardMethodAttachment;
import com.ruoyi.basic.pojo.Laboratory;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@@ -22,7 +24,7 @@
    List<StandardMethod> selectStandardMethods();
    int addStandardMethod(StandardMethod standardMethod);
    Integer addStandardMethod(StandardMethod standardMethod);
    int delStandardMethod(Integer id);
@@ -32,4 +34,12 @@
    List<StandardMethodList> selectStandardMethodEnum();
    List<StandardMethodAttachment> selectAttachments(Integer standardMethodId);
    StandardMethodAttachment uploadAttachment(Integer standardMethodId, MultipartFile file);
    boolean deleteAttachment(Long id);
    List<Laboratory> selectIndustryOptions();
}
basic-server/src/main/java/com/ruoyi/basic/service/StandardProductListService.java
@@ -31,7 +31,7 @@
     * @param tree æ ‡å‡†æ ‘路径,使用 {@code " - "} åˆ†éš”层级
     * @return {@code productList} ä¸ºç”ŸæˆåŽçš„æ£€éªŒé¡¹ç›®åˆ—表,{@code total} ä¸ºåˆ—表数量
     */
    Map<String, Object> selectStandardProductListByMethodId(Integer id, String tree);
    Map<String, Object> selectStandardProductListByMethodId(Integer id, String tree, Integer page, String keyword);
    IPage<StandardProductList> selectStandardProductByMethodId(Integer id, String tree, Integer page, String laboratory, String item, String items);
basic-server/src/main/java/com/ruoyi/basic/service/StandardTreeService.java
@@ -1,8 +1,13 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.FactoryDto;
import com.ruoyi.basic.dto.AddStandardTreeNodeDto;
import com.ruoyi.basic.dto.SampleTypeDto;
import com.ruoyi.basic.dto.StandardLibraryTreeNodeDto;
import com.ruoyi.basic.dto.UpdateStandardTreeNodeDto;
import com.ruoyi.basic.dto.StandardTreeTemplateBindingDto;
import com.ruoyi.basic.dto.StandardTreeTemplateBindingVo;
import com.ruoyi.basic.dto.TemplateInspectionItemDto;
import com.ruoyi.basic.pojo.StandardTree;
import org.springframework.web.multipart.MultipartFile;
@@ -16,10 +21,10 @@
*/
public interface StandardTreeService extends IService<StandardTree> {
    List<FactoryDto> selectStandardTreeList();
    List<StandardLibraryTreeNodeDto> selectStandardTreeList();
    int addStandardTree(StandardTree standardTree);
    int addStandardTree(AddStandardTreeNodeDto dto);
    int delStandardTree(String tree);
@@ -28,11 +33,17 @@
    int upStandardProducts(Map<String, Object> product);
    /**
     * æ ‡å‡†æ•°æŽ’序
     * æ ‡å‡†æ ‘排序
     * @param list
     * @return
     */
    boolean updateTreeSort(List<FactoryDto> list);
    boolean updateTreeSort(List<StandardLibraryTreeNodeDto> list);
    int updateStandardTree(StandardTree standardTree);
    int updateStandardTree(UpdateStandardTreeNodeDto dto);
    List<StandardTreeTemplateBindingVo> selectTemplateBindings(Integer standardTreeId);
    boolean saveTemplateBindings(StandardTreeTemplateBindingDto dto);
    List<TemplateInspectionItemDto> selectTemplateInspectionItems(Integer standardTreeId);
}
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardMethodServiceImpl.java
@@ -12,21 +12,36 @@
import com.ruoyi.basic.mapper.StandardMethodMapper;
import com.ruoyi.basic.mapper.StandardProductListMapper;
import com.ruoyi.basic.mapper.StructureItemParameterMapper;
import com.ruoyi.basic.mapper.StandardMethodIndustryMapper;
import com.ruoyi.basic.mapper.StandardMethodAttachmentMapper;
import com.ruoyi.basic.mapper.LaboratoryMapper;
import com.ruoyi.basic.mapper.StandardTreeMapper;
import com.ruoyi.basic.mapper.StandardTreeTemplateBindingMapper;
import com.ruoyi.basic.pojo.StandardMethod;
import com.ruoyi.basic.pojo.StandardMethodIndustry;
import com.ruoyi.basic.pojo.StandardMethodAttachment;
import com.ruoyi.basic.pojo.Laboratory;
import com.ruoyi.basic.pojo.StandardTree;
import com.ruoyi.basic.pojo.StandardTreeTemplateBinding;
import com.ruoyi.basic.pojo.StandardProductList;
import com.ruoyi.basic.pojo.StructureItemParameter;
import com.ruoyi.basic.service.StandardMethodService;
import com.ruoyi.basic.service.StandardProductListService;
import com.ruoyi.basic.service.StructureItemParameterService;
import lombok.AllArgsConstructor;
import com.ruoyi.basic.utils.StandardTreePath;
import com.ruoyi.basic.utils.StandardTreePathParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.exception.base.BaseException;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
 * @author Administrator
@@ -34,7 +49,6 @@
 * @createDate 2024-03-03 19:21:41
 */
@Service
@AllArgsConstructor
public class StandardMethodServiceImpl extends ServiceImpl<StandardMethodMapper, StandardMethod>
        implements StandardMethodService {
@@ -47,9 +61,51 @@
    private StructureItemParameterMapper structureItemParameterMapper;
    private StructureItemParameterService structureItemParameterService;
    private StandardMethodIndustryMapper standardMethodIndustryMapper;
    private StandardMethodAttachmentMapper standardMethodAttachmentMapper;
    private LaboratoryMapper laboratoryMapper;
    private StandardTreeMapper standardTreeMapper;
    private StandardTreeTemplateBindingMapper standardTreeTemplateBindingMapper;
    @Value("${file.path}")
    private String filePath;
    @Value("${wordUrl}")
    private String wordUrl;
    /**
     * ä¸šåŠ¡ä¾èµ–ç”±æž„é€ å™¨æ³¨å…¥ï¼›æ–‡ä»¶è·¯å¾„ç”± @Value ä»Žé…ç½®è¯»å–,不能参与 Lombok çš„全参构造器,
     * å¦åˆ™ Spring ä¼šæŠŠ String è¯¯è®¤ä¸ºéœ€è¦æ³¨å…¥çš„ Bean。
     */
    public StandardMethodServiceImpl(StandardMethodMapper standardMethodMapper,
                                     StandardProductListMapper standardProductListMapper,
                                     StandardProductListService standardProductListService,
                                     StructureItemParameterMapper structureItemParameterMapper,
                                     StructureItemParameterService structureItemParameterService,
                                     StandardMethodIndustryMapper standardMethodIndustryMapper,
                                     StandardMethodAttachmentMapper standardMethodAttachmentMapper,
                                     LaboratoryMapper laboratoryMapper,
                                     StandardTreeMapper standardTreeMapper,
                                     StandardTreeTemplateBindingMapper standardTreeTemplateBindingMapper) {
        this.standardMethodMapper = standardMethodMapper;
        this.standardProductListMapper = standardProductListMapper;
        this.standardProductListService = standardProductListService;
        this.structureItemParameterMapper = structureItemParameterMapper;
        this.structureItemParameterService = structureItemParameterService;
        this.standardMethodIndustryMapper = standardMethodIndustryMapper;
        this.standardMethodAttachmentMapper = standardMethodAttachmentMapper;
        this.laboratoryMapper = laboratoryMapper;
        this.standardTreeMapper = standardTreeMapper;
        this.standardTreeTemplateBindingMapper = standardTreeTemplateBindingMapper;
    }
    @Override
    public IPage<StandardMethod> selectStandardMethodList(Page page, StandardMethod standardMethod) {
        return standardMethodMapper.selectStandardMethodList(page, QueryWrappers.queryWrappers(standardMethod));
        IPage<StandardMethod> result = standardMethodMapper.selectStandardMethodList(page, QueryWrappers.queryWrappers(standardMethod));
        for (StandardMethod method : result.getRecords()) {
            method.setIndustryIds(selectIndustryIds(method.getId()));
        }
        return result;
    }
    @Override
@@ -58,25 +114,167 @@
    }
    @Override
    public int addStandardMethod(StandardMethod standardMethod) {
        int insert = standardMethodMapper.insert(standardMethod);
        return insert;
    @Transactional(rollbackFor = Exception.class)
    public Integer addStandardMethod(StandardMethod standardMethod) {
        standardMethod.setStructureTestObjectId(null);
        standardMethodMapper.insert(standardMethod);
        syncIndustries(standardMethod, Collections.<Integer>emptySet());
        return standardMethod.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delStandardMethod(Integer id) {
        int i = standardMethodMapper.deleteById(id);
        return i;
        StandardMethod method = standardMethodMapper.selectById(id);
        if (method == null) {
            throw new BaseException("标准不存在");
        }
        for (Integer industryId : selectIndustryIds(id)) {
            removeIndustryTree(method, industryId);
        }
        standardMethodIndustryMapper.delete(Wrappers.<StandardMethodIndustry>lambdaQuery()
                .eq(StandardMethodIndustry::getStandardMethodId, id));
        standardMethodAttachmentMapper.delete(Wrappers.<StandardMethodAttachment>lambdaQuery()
                .eq(StandardMethodAttachment::getStandardMethodId, id));
        return standardMethodMapper.deleteById(id);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int upStandardMethod(StandardMethod standardMethod) {
        StandardMethod oldStandardMethod = standardMethodMapper.selectById(standardMethod.getId());
        if (oldStandardMethod == null) {
            throw new BaseException("标准不存在");
        }
        if (!oldStandardMethod.getCode().equals(standardMethod.getCode())) {
            CompletableFuture.supplyAsync(() -> replaceMethod(oldStandardMethod.getCode(), standardMethod.getCode()));
        }
        int i = standardMethodMapper.updateById(standardMethod);
        syncIndustries(standardMethod, new LinkedHashSet<>(selectIndustryIds(oldStandardMethod.getId())));
        return i;
    }
    private List<Integer> selectIndustryIds(Integer methodId) {
        List<StandardMethodIndustry> list = standardMethodIndustryMapper.selectList(
                Wrappers.<StandardMethodIndustry>lambdaQuery()
                        .eq(StandardMethodIndustry::getStandardMethodId, methodId));
        List<Integer> ids = new ArrayList<>();
        for (StandardMethodIndustry item : list) {
            ids.add(item.getIndustryId());
        }
        return ids;
    }
    private void syncIndustries(StandardMethod method, Set<Integer> oldIds) {
        Set<Integer> newIds = method.getIndustryIds() == null ? new LinkedHashSet<Integer>()
                : new LinkedHashSet<>(method.getIndustryIds());
        for (Integer industryId : newIds) {
            if (industryId == null || laboratoryMapper.selectById(industryId) == null) {
                throw new BaseException("行业不存在");
            }
        }
        for (Integer oldId : oldIds) {
            if (!newIds.contains(oldId)) {
                removeIndustryTree(method, oldId);
                standardMethodIndustryMapper.delete(Wrappers.<StandardMethodIndustry>lambdaQuery()
                        .eq(StandardMethodIndustry::getStandardMethodId, method.getId())
                        .eq(StandardMethodIndustry::getIndustryId, oldId));
            }
        }
        for (Integer industryId : newIds) {
            Laboratory lab = laboratoryMapper.selectById(industryId);
            Long count = standardMethodIndustryMapper.selectCount(Wrappers.<StandardMethodIndustry>lambdaQuery()
                    .eq(StandardMethodIndustry::getStandardMethodId, method.getId())
                    .eq(StandardMethodIndustry::getIndustryId, industryId));
            if (count == 0) {
                StandardMethodIndustry relation = new StandardMethodIndustry();
                relation.setStandardMethodId(method.getId());
                relation.setIndustryId(industryId);
                standardMethodIndustryMapper.insert(relation);
            }
            StandardTree tree = standardTreeMapper.selectOne(Wrappers.<StandardTree>lambdaQuery()
                    .eq(StandardTree::getLaboratory, lab.getLaboratoryName())
                    .eq(StandardTree::getStandardMethodId, method.getId())
                    .isNull(StandardTree::getSampleType).isNull(StandardTree::getModel));
            if (tree == null) {
                tree = new StandardTree();
                tree.setLaboratory(lab.getLaboratoryName());
                tree.setSample(buildLabel(method));
                tree.setStandardMethodId(method.getId());
                tree.setSort(0);
                standardTreeMapper.insert(tree);
            } else if (!buildLabel(method).equals(tree.getSample())) {
                StandardTree update = new StandardTree();
                update.setId(tree.getId());
                update.setSample(buildLabel(method));
                standardTreeMapper.updateById(update);
            }
        }
    }
    private void removeIndustryTree(StandardMethod method, Integer industryId) {
        Laboratory lab = laboratoryMapper.selectById(industryId);
        if (lab == null) return;
        StandardTree tree = standardTreeMapper.selectOne(Wrappers.<StandardTree>lambdaQuery()
                .eq(StandardTree::getLaboratory, lab.getLaboratoryName())
                .eq(StandardTree::getStandardMethodId, method.getId())
                .isNull(StandardTree::getSampleType).isNull(StandardTree::getModel));
        if (tree != null) {
            standardTreeTemplateBindingMapper.delete(Wrappers.<StandardTreeTemplateBinding>lambdaQuery()
                    .eq(StandardTreeTemplateBinding::getStandardTreeId, tree.getId()));
            standardProductListMapper.delete(Wrappers.<StandardProductList>lambdaQuery()
                    .eq(StandardProductList::getLaboratory, lab.getLaboratoryName())
                    .eq(StandardProductList::getSample, tree.getSample())
                    .isNull(StandardProductList::getSampleType).isNull(StandardProductList::getModel));
            standardTreeMapper.deleteById(tree.getId());
        }
    }
    private String buildLabel(StandardMethod method) {
        return method.getCode().trim();
    }
    @Override
    public List<StandardMethodAttachment> selectAttachments(Integer standardMethodId) {
        return standardMethodAttachmentMapper.selectList(Wrappers.<StandardMethodAttachment>lambdaQuery()
                .eq(StandardMethodAttachment::getStandardMethodId, standardMethodId)
                .orderByAsc(StandardMethodAttachment::getSort, StandardMethodAttachment::getId));
    }
    @Override
    public StandardMethodAttachment uploadAttachment(Integer standardMethodId, MultipartFile file) {
        if (standardMethodMapper.selectById(standardMethodId) == null) throw new BaseException("标准不存在");
        if (file == null || file.isEmpty() || file.getSize() > 10 * 1024 * 1024) throw new BaseException("附件不能为空且不能超过10MB");
        String originalName = file.getOriginalFilename();
        if (originalName == null || !originalName.matches("(?i).+\\.(jpg|jpeg|png|gif|doc|docx|xls|xlsx|ppt|pptx|pdf|zip|rar)$")) throw new BaseException("不支持的附件格式");
        try {
            boolean image = originalName.matches("(?i).+\\.(jpg|jpeg|png|gif)$");
            File directory = new File(image ? filePath : wordUrl);
            if (!directory.exists()) directory.mkdirs();
            String fileUrl = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmssSSS")) + "-" + originalName;
            file.transferTo(new File(directory, fileUrl));
            StandardMethodAttachment attachment = new StandardMethodAttachment();
            attachment.setStandardMethodId(standardMethodId);
            attachment.setFileName(originalName);
            attachment.setFileUrl(fileUrl);
            attachment.setSort(selectAttachments(standardMethodId).size());
            standardMethodAttachmentMapper.insert(attachment);
            return attachment;
        } catch (IOException e) {
            throw new BaseException("附件上传失败");
        }
    }
    @Override
    public boolean deleteAttachment(Long id) {
        return standardMethodAttachmentMapper.deleteById(id) > 0;
    }
    @Override
    public List<Laboratory> selectIndustryOptions() {
        return laboratoryMapper.selectList(Wrappers.<Laboratory>lambdaQuery()
                .select(Laboratory::getId, Laboratory::getLaboratoryName, Laboratory::getSort)
                .orderByAsc(Laboratory::getSort, Laboratory::getId));
    }
    //编辑method后全部替换
@@ -172,33 +370,16 @@
        if (tree == null || tree.trim().isEmpty()) {
            return Collections.emptyMap();
        }
        String[] trees = tree.split(" - ");
        Map<String, List<?>> map = new HashMap<>();
        String str = "";
        List<StandardMethodList> standardMethodLists = new ArrayList<>();
        switch (trees.length){
            case 5:
                str += "\"" + trees[2] + "\",\"" + trees[3] + "\",\"" + trees[4] + "\"";
                standardMethodLists.addAll(standardMethodMapper.selectStandardMethodLists(str));
                standardMethodLists.addAll(standardMethodMapper.selectStandardMethodLists("\"" + trees[2] + "\",\"" + trees[3] + "\""));
                standardMethodLists.addAll(standardMethodMapper.selectStandardMethodLists("\"" + trees[2] + "\""));
                break;
            case 4:
                str += "\"" + trees[2] + "\",\"" + trees[3] + "\"";
                standardMethodLists.addAll(standardMethodMapper.selectStandardMethodLists(str));
                standardMethodLists.addAll(standardMethodMapper.selectStandardMethodLists("\"" + trees[2] + "\""));
                break;
            case 3:
                str += "\"" + trees[2] + "\"";
                standardMethodLists.addAll(standardMethodMapper.selectStandardMethodLists3(str));
                break;
            default:
                map.put("standardMethodList", null);
                return map;
        StandardTreePath path = StandardTreePathParser.parse(tree);
        String industry = path.getIndustry();
        List<StandardMethodList> standardMethodLists = new ArrayList<>(standardMethodMapper.selectStandardMethodListsByIndustry(industry));
        // æŒ‰ ID åŽ»é‡ï¼Œä¿æŒç¨³å®šæŽ’åº
        Map<Integer, StandardMethodList> byId = new LinkedHashMap<>();
        for (StandardMethodList m : standardMethodLists) {
            byId.putIfAbsent(m.getId(), m);
        }
        standardMethodLists.addAll(standardMethodMapper.selectStandardMethodListsByNull(str));
        List<StandardMethodList> collect = standardMethodLists.stream().distinct().collect(Collectors.toList());
        map.put("standardMethodList", collect);
        Map<String, List<?>> map = new HashMap<>();
        map.put("standardMethodList", new ArrayList<>(byId.values()));
        return map;
    }
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardProductListServiceImpl.java
@@ -1,7 +1,6 @@
package com.ruoyi.basic.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -10,7 +9,6 @@
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.basic.dto.CopyStandardProductListDto;
import com.ruoyi.basic.dto.InsSampleReceiveDto;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ResetTreeDragDTO;
import com.ruoyi.basic.mapper.IfsInventoryQuantityMapper;
import com.ruoyi.basic.mapper.StandardProductListMapper;
@@ -18,8 +16,9 @@
import com.ruoyi.basic.mapper.StandardTreeMapper;
import com.ruoyi.basic.pojo.StandardProductList;
import com.ruoyi.basic.pojo.StandardProductListSupplierAsk;
import com.ruoyi.basic.pojo.StandardTree;
import com.ruoyi.basic.service.StandardProductListService;
import com.ruoyi.basic.utils.StandardTreePath;
import com.ruoyi.basic.utils.StandardTreePathParser;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -57,13 +56,36 @@
    @Override
    public List<StandardProductList> selectStandardProductList(InsSampleReceiveDto insSample) {
        // æ˜¯å¦æ²¡æœ‰äº§å“
        if (insSample == null || insSample.getStandardMethodListId() == null) {
            throw new BaseException("检验标准不能为空");
        }
        // æ–°ç–†æ–°èšä¸¤çº§æ ‘优先按“行业 + æ ‡å‡†â€è§„范路径查询;无法解析时保留历史五级逻辑。
        boolean twoLevelOrder = false;
        boolean isNoSample = false;
//        String[] models = insSample.getModel().split("-(?=[^-]*$)");//拆分最后一个【-】
        String model = insSample.getModel();
        String modelNum = insSample.getModelNum();
        List<StandardProductList> list = standardProductListMapper.selectDetail(insSample.getStandardMethodListId(), 1, model, insSample.getIsCableTag());
        if (list.size() == 0) {
        List<StandardProductList> list = Collections.emptyList();
        if (StringUtils.isNotBlank(insSample.getFactory())) {
            try {
                StandardTreePath path = StandardTreePathParser.parse(insSample.getFactory());
                if (path.getStandard() != null) {
                    String canonicalTree = StandardTreePathParser.canonical(path.getIndustry(), path.getStandard());
                    list = standardProductListMapper.selectDetail2(
                            insSample.getStandardMethodListId(), 1, canonicalTree, insSample.getIsCableTag());
                    insSample.setLaboratory(path.getIndustry());
                    insSample.setSampleType(null);
                    insSample.setSample(path.getStandard());
                    insSample.setModel(null);
                    twoLevelOrder = true;
                }
            } catch (BaseException ignored) {
                // åŽ†å²äº”çº§è·¯å¾„ç»§ç»­èµ°åŽŸæœ‰å…¼å®¹æŸ¥è¯¢ã€‚
            }
        }
        if (!twoLevelOrder) {
            list = standardProductListMapper.selectDetail(insSample.getStandardMethodListId(), 1, model, insSample.getIsCableTag());
        }
        if (!twoLevelOrder && list.size() == 0) {
            if (Objects.equals(insSample.getFactory(), "") || insSample.getFactory() == null) {
                return null;
            }
@@ -85,8 +107,8 @@
                }
            }
        }
        String[] split1 = insSample.getFactory().split(" - ");
        if (!isNoSample) {
        if (!twoLevelOrder && !isNoSample) {
            String[] split1 = insSample.getFactory().split(" - ");
            //判断长度
            if (split1.length > 4) {
                if (ObjectUtils.isNotEmpty(split1[3])) {
@@ -96,9 +118,14 @@
                }
            }
        }
        final boolean currentTwoLevelOrder = twoLevelOrder;
        list = list.stream().filter(a -> {
                    try {
                        if (a.getSection() != null && !Objects.equals(a.getSection(), "")) {
                            // ä¸¤çº§ä¸‹å•不再输入型号,不能因无法计算型号区间而丢弃整个检验项。
                            if (currentTwoLevelOrder && StringUtils.isBlank(modelNum)) {
                                return true;
                            }
                            List<String> sections = JSON.parseArray(a.getSection(), String.class);// åŒºé—´
                            List<String> cores = JSON.parseArray(a.getCores(), String.class); // èŠ¯æ•°
                            List<String> conductorMaterials = JSON.parseArray(a.getConductorMaterial(), String.class); // å¯¼ä½“材质
@@ -256,234 +283,151 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> selectStandardProductListByMethodId(Integer id, String tree) {
        // å°†â€œå·¥åŽ‚ - å®žéªŒå®¤ - æ ·å“ç±»åˆ« - æ ·å“ - åž‹å·â€è·¯å¾„拆成各级节点。
        String[] trees = tree.split(" - ");
        // å››çº§åŠä»¥ä¸ŠèŠ‚ç‚¹éœ€è¦æŒ‰æ•°æ®åº“ä¸­å·²ä¿å­˜çš„ sort æŽ’序返回。
        boolean isDrag = false;
        // å­˜æ”¾æ ¹æ®ç»“构参数表重新生成的检验项目。
        List<StandardProductList> list = new ArrayList<>();
        if (trees.length == 3) {
            // ä¸‰çº§èŠ‚ç‚¹åªå®šä½åˆ°æ ·å“ç±»åˆ«ï¼Œéœ€è¦ç»§ç»­å±•å¼€è¯¥ç±»åˆ«ä¸‹çš„æ ·å“å’Œåž‹å·ã€‚
            List<StandardTree> treeList = new ArrayList<>();
            StandardTree standardTree = new StandardTree();
            standardTree.setFactory(trees[0]);
            standardTree.setLaboratory(trees[1]);
            standardTree.setSampleType(trees[2]);
            // æŸ¥è¯¢è¯¥æ ·å“ç±»åˆ«å…³è”的产品(样品)名称。
            List<ProductDto> pList = standardTreeMapper.selectPList(trees[2]);
            if (pList.isEmpty() || pList.get(0) == null) {
                // æ²¡æœ‰äº§å“æ—¶ï¼Œç›´æŽ¥ä½¿ç”¨è¯¥å®žéªŒå®¤å’Œæ ·å“ç±»åˆ«å·²é…ç½®çš„æ ‡å‡†æ ‘节点。
                List<StandardTree> treeList1 = standardTreeMapper.selectList(Wrappers.<StandardTree>lambdaQuery().eq(StandardTree::getLaboratory, trees[1]).eq(StandardTree::getSampleType, trees[2]));
                if (treeList1.isEmpty()) {
                    // æ ‡å‡†æ ‘也不存在时,保留三级节点作为兜底查询条件。
                    treeList.add(standardTree);
                } else {
                    treeList.addAll(treeList1);
                }
            } else {
                // æŒ‰äº§å“é€ä¸ªæŸ¥æ‰¾å·²é…ç½®çš„型号;未配置时创建一个只有样品信息的兜底节点。
                for (ProductDto p : pList) {
                    standardTree.setSample(p.getName());
                    List<StandardTree> treeList1 = standardTreeMapper.selectList(Wrappers.<StandardTree>lambdaQuery().eq(StandardTree::getLaboratory, trees[1]).eq(StandardTree::getSampleType, trees[2]).eq(StandardTree::getSample, p.getName()));
                    if (treeList1.isEmpty()) {
                        treeList.add(JSON.parseObject(JSON.toJSONString(standardTree), StandardTree.class));
                    } else {
                        treeList.addAll(treeList1);
                    }
                }
            }
            // ä¸ºæ¯ä¸ªå±•开后的“样品 + åž‹å·â€èŠ‚ç‚¹æŸ¥è¯¢é€‚ç”¨çš„æ£€éªŒé¡¹ç›®ã€‚
            for (StandardTree standardTree2 : treeList) {
                String tree2 = trees[0] + " - " + trees[1] + " - " + trees[2] + " - " + standardTree2.getSample() + " - " + standardTree2.getModel();
                // æŸ¥è¯¢é€‚用于样品类别的通用项目及当前样品/型号的项目。
                list.addAll(standardTreeMapper.selectStandardProductListByTree("\"" + trees[2] + "\"", standardTree2.getSample(), standardTree2.getModel(), tree2, trees[1]));
                // æŸ¥è¯¢åŒæ—¶é™å®šæ ·å“ç±»åˆ«å’Œæ ·å“åç§°çš„项目。
                list.addAll(standardTreeMapper.selectStandardProductListByTree2("\"" + trees[2] + "\",\"" + standardTree2.getSample() + "\"", standardTree2.getSample(), standardTree2.getModel(), tree2, trees[1]));
            }
        } else if (trees.length == 4) {
            // å››çº§èŠ‚ç‚¹å·²å®šä½åˆ°æ ·å“ï¼ŒåŽç»­æŒ‰å·²ä¿å­˜çš„ sort æŽ’序返回。
            isDrag = true;
            // æŸ¥è¯¢è¯¥æ ·å“å·²é…ç½®çš„型号节点。
            List<StandardTree> treeList = standardTreeMapper.selectList(Wrappers.<StandardTree>lambdaQuery().eq(StandardTree::getLaboratory, trees[1]).eq(StandardTree::getSampleType, trees[2]).eq(StandardTree::getSample, trees[3]));
            if (treeList.isEmpty()) {
                // æ²¡æœ‰åž‹å·é…ç½®æ—¶ï¼Œåˆ›å»ºä¸å«åž‹å·çš„节点,仍可查询通用项目。
                StandardTree standardTree = new StandardTree();
                standardTree.setFactory(trees[0]);
                standardTree.setLaboratory(trees[1]);
                standardTree.setSampleType(trees[2]);
                standardTree.setSample(trees[3]);
                treeList.add(standardTree);
            }
            // é’ˆå¯¹æ ·å“çš„æ¯ä¸ªåž‹å·ï¼Œåˆå¹¶é€šç”¨é¡¹ç›®å’Œæ ·å“ä¸“属项目。
            for (StandardTree standardTree : treeList) {
                String str = tree + " - " + standardTree.getModel();
                list.addAll(standardTreeMapper.selectStandardProductListByTree("\"" + trees[2] + "\"", standardTree.getSample(), standardTree.getModel(), str, trees[1]));
                list.addAll(standardTreeMapper.selectStandardProductListByTree2("\"" + trees[2] + "\",\"" + trees[3] + "\"", standardTree.getSample(), standardTree.getModel(), str, trees[1]));
            }
        } else {
            // äº”级节点已明确样品和型号,直接查询该节点适用的项目。
            isDrag = true;
            list.addAll(standardTreeMapper.selectStandardProductListByTree("\"" + trees[2] + "\"", trees[3].equals("null") ? null : trees[3], trees[4], tree, trees[1]));
            list.addAll(standardTreeMapper.selectStandardProductListByTree2("\"" + trees[2] + "\",\"" + trees[3] + "\"", trees[3].equals("null") ? null : trees[3], trees[4], tree, trees[1]));
    public Map<String, Object> selectStandardProductListByMethodId(Integer id, String tree, Integer page, String keyword) {
        if (id == null) {
            throw new BaseException("检验标准不能为空");
        }
        // ä¸ºæ–°ç”Ÿæˆçš„项目预先分配雪花 ID;匹配到旧配置后会替换为旧记录 ID。
        for (StandardProductList productList : list) {
            productList.setId(IdWorker.getId());
        StandardTreePath path = StandardTreePathParser.parse(tree);
        if (path.getStandard() == null) {
            throw new BaseException("请选择标准节点");
        }
        List<StandardProductList> standardProductLists;
        String canonicalTree = StandardTreePathParser.canonical(path.getIndustry(), path.getStandard());
        List<StandardProductList> generated = standardTreeMapper.selectStandardProductListByIndustry(
                path.getIndustry(), path.getStandard(), canonicalTree);
        List<StandardProductList> saved = standardProductListMapper.selectList(
                Wrappers.<StandardProductList>lambdaQuery()
                        .eq(StandardProductList::getStandardMethodListId, id)
                        .eq(StandardProductList::getTree, canonicalTree)
                        .orderByAsc(StandardProductList::getSort));
        if (isDrag) {
            // æŸ¥è¯¢å½“前节点及其子节点的旧配置,并按 sort è¯»å–以保留拖拽后的顺序。
            standardProductLists = standardProductListMapper.selectList(Wrappers.<StandardProductList>lambdaQuery()
                    .eq(StandardProductList::getStandardMethodListId, id)
                    .like(StandardProductList::getTree, tree)
                    .orderByAsc(StandardProductList::getSort));
        } else {
            // ä¸‰çº§èŠ‚ç‚¹æ²¡æœ‰æ‹–æ‹½é¡ºåºï¼Œåªè¯»å–ç”¨äºŽè¦†ç›–é»˜è®¤å€¼çš„æ—§é…ç½®ã€‚
            standardProductLists = standardProductListMapper.selectList(Wrappers.<StandardProductList>lambdaQuery()
                    .eq(StandardProductList::getStandardMethodListId, id)
                    .like(StandardProductList::getTree, tree));
        }
        // å°†æ—§é¡¹ç›®ä¸­äººå·¥ç»´æŠ¤çš„配置覆盖到本次重新生成的对应项目。
        for (StandardProductList sp : standardProductLists) {
            for (StandardProductList pl : list) {
                // åŠå¾„候选值只有一个时,将其作为默认半径。
                String radiusList = pl.getRadiusList();
                if (StringUtils.isNotBlank(radiusList) && !radiusList.equals("null") && !radiusList.equals("\"\"")) {
                    JSONArray jsonArray = JSON.parseArray(radiusList);
                    List<String> radius = jsonArray.toJavaList(String.class);
                    if (CollectionUtils.isNotEmpty(radius) && radius.size() == 1) {
                        pl.setRadius(radius.get(0));
                    }
                }
                // ä»¥æ£€éªŒé¡¹ç›®ã€å­é¡¹ã€åž‹å·ã€æ ·å“å’Œç»“构参数 ID åˆ¤æ–­æ–°æ—§é¡¹ç›®æ˜¯å¦å¯¹åº”。
                if (Objects.equals(sp.getInspectionItem(), pl.getInspectionItem())
                        && Objects.equals((sp.getInspectionItemSubclass() == null) ? "" : sp.getInspectionItemSubclass(), pl.getInspectionItemSubclass() == null ? "" : pl.getInspectionItemSubclass())
//                        && Objects.equals(sp.getSample(), pl.getSample())
                        && Objects.equals(sp.getModel(), pl.getModel())
                        && sp.getTree().indexOf(pl.getSample() == null ? "null" : pl.getSample()) > -1
                        && Objects.equals(sp.getStructureItemParameterId(), pl.getStructureItemParameterId())) {
                    // å¤ç”¨æ—§ ID å’ŒæŽ’序,确保本次同步后原有配置仍关联到同一条项目记录。
                    pl.setId(sp.getId());
                    pl.setSort(sp.getSort());
                    // æ—§è®°å½•存在值时优先保留;状态为空时按标准 ID è®¾ç½®é»˜è®¤çŠ¶æ€ã€‚
                    if (sp.getState() != null && !sp.getState().equals("")) {
                        pl.setState(sp.getState());
                    } else {
                        pl.setState(id == 0 ? 1 : 0);
                    }
                    if (sp.getMethodS() != null && !sp.getMethodS().equals("")) {
                        pl.setMethod(sp.getMethodS());
                    }
                    if (sp.getRadius() != null && !sp.getRadius().equals("")) {
                        pl.setRadius(sp.getRadius());
                    }
                    if (sp.getRates() != null && !sp.getRates().equals("")) {
                        pl.setRates(sp.getRates());
                    }
                    if (sp.getAsk() != null && !sp.getAsk().equals("")) {
                        pl.setAsk(sp.getAsk());
                    }
                    if (sp.getTell() != null && !sp.getTell().equals("")) {
                        pl.setTell(sp.getTell());
                    }
                    if (sp.getPrice() != null && !sp.getPrice().equals("")) {
                        pl.setPrice(sp.getPrice());
                    }
                    if (sp.getManHour() != null && !sp.getManHour().equals("")) {
                        pl.setManHour(sp.getManHour());
                    }
                    if (sp.getSection() != null && !sp.getSection().equals("")) {
                        pl.setSection(sp.getSection());
                    }
                    if (sp.getCores() != null && !sp.getCores().equals("")) {
                        pl.setCores(sp.getCores());
                    }
                    if (sp.getConductorMaterial() != null && !sp.getConductorMaterial().equals("")) {
                        pl.setConductorMaterial(sp.getConductorMaterial());
                    }
                    if (sp.getConductorType() != null && !sp.getConductorType().equals("")) {
                        pl.setConductorType(sp.getConductorType());
                    }
                    if (sp.getTemplateId() != null && !sp.getTemplateId().equals("")) {
                        pl.setTemplateId(sp.getTemplateId());
                    }
                    break;
                }
        Map<Integer, StandardProductList> savedByParameterId = new HashMap<>();
        for (StandardProductList item : saved) {
            if (item.getStructureItemParameterId() != null) {
                savedByParameterId.put(item.getStructureItemParameterId(), item);
            }
        }
        // è®°å½•本次同步的创建人和更新人。
        Integer userId = SecurityUtils.getUserId().intValue();
        if (trees.length == 5) {
            // äº”级节点只重建当前精确路径的数据。
            standardProductListMapper.delete(Wrappers.<StandardProductList>lambdaUpdate()
                    .eq(StandardProductList::getStandardMethodListId, id)
                    .eq(StandardProductList::getTree, tree));
        } else {
            // ä¸‰çº§ã€å››çº§èŠ‚ç‚¹é‡å»ºå½“å‰è·¯å¾„åŠå…¶æ‰€æœ‰å­èŠ‚ç‚¹çš„æ•°æ®ã€‚
            standardProductListMapper.delete(Wrappers.<StandardProductList>lambdaUpdate()
                    .eq(StandardProductList::getStandardMethodListId, id)
                    .like(StandardProductList::getTree, tree));
        }
        // è¡¥é½æ‰¹é‡ä¿å­˜æ‰€éœ€çš„æ ‘节点、用户和检验标准字段。
        List<StandardProductList> productLists = list.stream().map(a -> {
            a.setFactory(trees[0]);
            a.setLaboratory(trees[1]);
            a.setSampleType(trees[2]);
            a.setCreateUser(userId);
            a.setUpdateUser(userId);
            a.setStandardMethodListId(id);
            return a;
        }).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(productLists)) {
            // æ‰¹é‡å†™å…¥é‡æ–°ç”ŸæˆåŽçš„æ£€éªŒé¡¹ç›®ã€‚
            baseMapper.saveBatchProductLists(productLists);
        }
        // å…ˆæŒ‰å·¥æ—¶åˆ†ç»„中的数字升序排列;空分组排到最后。
        Collections.sort(list, (o1, o2) -> {
            String field1 = o1.getManHourGroup();
            String field2 = o2.getManHourGroup();
            boolean isEmpty1 = field1 == null || field1.isEmpty();
            boolean isEmpty2 = field2 == null || field2.isEmpty();
            if (isEmpty1 && isEmpty2) {
                return 0;
            } else if (isEmpty1) {
                return 1;
            } else if (isEmpty2) {
                return -1;
        for (StandardProductList item : generated) {
            applySingleRadiusDefault(item);
            StandardProductList old = savedByParameterId.get(item.getStructureItemParameterId());
            if (old != null && sameInspectionItem(old, item)) {
                mergeSavedConfiguration(old, item);
            } else {
                int num1 = extractNumber(field1);
                int num2 = extractNumber(field2);
                return Integer.compare(num1, num2);
                item.setId(IdWorker.getId());
                item.setState(id == 0 ? 1 : 0);
            }
        });
        if (isDrag) {
            // å››çº§ã€äº”级节点最终按已保存的拖拽排序覆盖工时分组排序。
            list.sort((o1, o2) -> (o1.getSort() == null ? 0 : o1.getSort())
                    - (o2.getSort() == null ? 0 : o2.getSort()));
            item.setFactory(null);
            item.setLaboratory(path.getIndustry());
            item.setSampleType(null);
            item.setSample(path.getStandard());
            item.setModel(null);
            item.setTree(canonicalTree);
            item.setCreateUser(userId);
            item.setUpdateUser(userId);
            item.setStandardMethodListId(id);
        }
        // æŒ‰å‰ç«¯çº¦å®šè¿”回项目列表和数量。
        standardProductListMapper.delete(Wrappers.<StandardProductList>lambdaQuery()
                .eq(StandardProductList::getStandardMethodListId, id)
                .eq(StandardProductList::getTree, canonicalTree));
        if (CollectionUtils.isNotEmpty(generated)) {
            baseMapper.saveBatchProductLists(generated);
        }
        generated.sort((left, right) -> {
            int leftSort = left.getSort() == null ? Integer.MAX_VALUE : left.getSort();
            int rightSort = right.getSort() == null ? Integer.MAX_VALUE : right.getSort();
            if (leftSort != rightSort) {
                return Integer.compare(leftSort, rightSort);
            }
            return compareManHourGroup(left.getManHourGroup(), right.getManHourGroup());
        });
        String search = keyword == null ? "" : keyword.trim().toLowerCase(Locale.ROOT);
        List<StandardProductList> filtered = generated.stream()
                .filter(item -> search.isEmpty()
                        || containsIgnoreCase(item.getInspectionItem(), search)
                        || containsIgnoreCase(item.getInspectionItemSubclass(), search))
                .collect(Collectors.toList());
        int total = filtered.size();
        int currentPage = page == null || page < 1 ? 1 : page;
        int fromIndex = Math.min((currentPage - 1) * 100, total);
        int toIndex = Math.min(fromIndex + 100, total);
        Map<String, Object> map = new HashMap<>();
        map.put("productList", list);
        map.put("total", list.size());
        map.put("productList", new ArrayList<>(filtered.subList(fromIndex, toIndex)));
        map.put("total", total);
        return map;
    }
    private int extractNumber(String s) {
        // ä»Žå­—符串中提取数字的逻辑,这里假设字段的格式是 "text<number>"
        String number = s;
        if (!s.matches("\\d+")) {
            number = s.replaceAll("\\D", "");
    private boolean sameInspectionItem(StandardProductList left, StandardProductList right) {
        return Objects.equals(left.getInspectionItem(), right.getInspectionItem())
                && Objects.equals(defaultString(left.getInspectionItemSubclass()), defaultString(right.getInspectionItemSubclass()));
    }
    private void applySingleRadiusDefault(StandardProductList item) {
        String radiusList = item.getRadiusList();
        if (StringUtils.isBlank(radiusList) || "null".equals(radiusList) || "\"\"".equals(radiusList)) {
            return;
        }
        return Integer.parseInt(number);
        try {
            List<String> values = JSON.parseArray(radiusList, String.class);
            if (values != null && values.size() == 1) {
                item.setRadius(values.get(0));
            }
        } catch (Exception ignored) {
            // åŽ†å²è„æ•°æ®ä¸å½±å“æ£€éªŒé¡¹åˆ—è¡¨ç”Ÿæˆã€‚
        }
    }
    private void mergeSavedConfiguration(StandardProductList old, StandardProductList target) {
        target.setId(old.getId());
        target.setSort(old.getSort());
        target.setState(old.getState() == null ? 0 : old.getState());
        if (StringUtils.isNotBlank(old.getMethodS())) {
            target.setMethodS(old.getMethodS());
            target.setMethod(old.getMethodS());
        }
        if (StringUtils.isNotBlank(old.getRadius())) target.setRadius(old.getRadius());
        if (StringUtils.isNotBlank(old.getRates())) target.setRates(old.getRates());
        if (StringUtils.isNotBlank(old.getAsk())) target.setAsk(old.getAsk());
        if (StringUtils.isNotBlank(old.getTell())) target.setTell(old.getTell());
        if (StringUtils.isNotBlank(old.getPrice())) target.setPrice(old.getPrice());
        if (StringUtils.isNotBlank(old.getManHour())) target.setManHour(old.getManHour());
        if (StringUtils.isNotBlank(old.getSection())) target.setSection(old.getSection());
        if (StringUtils.isNotBlank(old.getCores())) target.setCores(old.getCores());
        if (StringUtils.isNotBlank(old.getConductorMaterial())) target.setConductorMaterial(old.getConductorMaterial());
        if (StringUtils.isNotBlank(old.getConductorType())) target.setConductorType(old.getConductorType());
        if (old.getTemplateId() != null) target.setTemplateId(old.getTemplateId());
    }
    private int compareManHourGroup(String left, String right) {
        boolean leftEmpty = StringUtils.isBlank(left);
        boolean rightEmpty = StringUtils.isBlank(right);
        if (leftEmpty && rightEmpty) return 0;
        if (leftEmpty) return 1;
        if (rightEmpty) return -1;
        return Integer.compare(extractNumber(left), extractNumber(right));
    }
    private boolean containsIgnoreCase(String value, String search) {
        return value != null && value.toLowerCase(Locale.ROOT).contains(search);
    }
    private String defaultString(String value) {
        return value == null ? "" : value;
    }
    private int extractNumber(String s) {
        String number = s == null ? "" : s.replaceAll("\\D", "");
        if (number.isEmpty()) {
            return Integer.MAX_VALUE;
        }
        try {
            return Integer.parseInt(number);
        } catch (NumberFormatException ignored) {
            return Integer.MAX_VALUE;
        }
    }
    @Override
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardTemplateServiceImpl.java
@@ -7,7 +7,9 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.StandardTemplateMapper;
import com.ruoyi.basic.mapper.StandardTreeTemplateBindingMapper;
import com.ruoyi.basic.pojo.StandardTemplate;
import com.ruoyi.basic.pojo.StandardTreeTemplateBinding;
import com.ruoyi.basic.service.StandardTemplateService;
import com.ruoyi.common.numgen.NumberGenerator;
import com.ruoyi.common.utils.GZipUtil;
@@ -29,6 +31,8 @@
    implements StandardTemplateService{
    private StandardTemplateMapper standardTemplateMapper;
    private StandardTreeTemplateBindingMapper standardTreeTemplateBindingMapper;
    private final NumberGenerator<StandardTemplate> numberGenerator;
@@ -71,12 +75,18 @@
        if (count > 0) {
            throw new RuntimeException("该模板已绑定检验项目参数,禁止删除");
        }
        Long bindingCount = standardTreeTemplateBindingMapper.selectCount(Wrappers.<StandardTreeTemplateBinding>lambdaQuery()
                .eq(StandardTreeTemplateBinding::getTemplateId, id));
        if (bindingCount > 0) {
            throw new RuntimeException("该模板已绑定标准库,禁止删除");
        }
        return standardTemplateMapper.deleteById(id);
    }
    @Override
    public List<StandardTemplate> getStandardTemplate() {
        return standardTemplateMapper.selectList(Wrappers.<StandardTemplate>lambdaQuery().select(StandardTemplate::getId,StandardTemplate::getName));
        return standardTemplateMapper.selectList(Wrappers.<StandardTemplate>lambdaQuery()
                .select(StandardTemplate::getId, StandardTemplate::getName, StandardTemplate::getNumber, StandardTemplate::getRemark));
    }
    @Override
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardTreeServiceImpl.java
@@ -1,32 +1,50 @@
package com.ruoyi.basic.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.FactoryDto;
import com.ruoyi.basic.dto.LaboratoryDto;
import com.ruoyi.basic.dto.AddStandardTreeNodeDto;
import com.ruoyi.basic.dto.SampleTypeDto;
import com.ruoyi.basic.dto.StandardLibraryTreeNodeDto;
import com.ruoyi.basic.dto.StandardLibraryTreeRowDto;
import com.ruoyi.basic.dto.UpdateStandardTreeNodeDto;
import com.ruoyi.basic.dto.StandardTreeTemplateBindingDto;
import com.ruoyi.basic.dto.StandardTreeTemplateBindingVo;
import com.ruoyi.basic.dto.TemplateBindingItemDto;
import com.ruoyi.basic.dto.TemplateInspectionItemDto;
import com.ruoyi.basic.mapper.LaboratoryMapper;
import com.ruoyi.basic.mapper.StandardProductListMapper;
import com.ruoyi.basic.mapper.StandardTreeMapper;
import com.ruoyi.basic.mapper.StandardTreeTemplateBindingMapper;
import com.ruoyi.basic.mapper.StructureItemParameterMapper;
import com.ruoyi.basic.mapper.StructureTestObjectMapper;
import com.ruoyi.basic.pojo.Laboratory;
import com.ruoyi.basic.pojo.StandardProductList;
import com.ruoyi.basic.pojo.StandardTree;
import com.ruoyi.basic.pojo.StandardTemplate;
import com.ruoyi.basic.pojo.StandardTreeTemplateBinding;
import com.ruoyi.basic.pojo.StructureItemParameter;
import com.ruoyi.basic.pojo.StructureTestObject;
import com.ruoyi.basic.service.StandardProductListService;
import com.ruoyi.basic.service.StandardTreeService;
import com.ruoyi.basic.service.StructureTestObjectService;
import com.ruoyi.basic.service.StandardTemplateService;
import com.ruoyi.basic.utils.StandardTreePath;
import com.ruoyi.basic.utils.StandardTreePathParser;
import com.ruoyi.basic.utils.StandardTemplateInspectionItemParser;
import com.ruoyi.common.exception.base.BaseException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.HashSet;
import java.util.Comparator;
import java.util.Set;
/**
 * @author Administrator
@@ -43,62 +61,155 @@
    private StandardProductListMapper standardProductListMapper;
    private StandardProductListService standardProductListService;
    private StructureItemParameterMapper structureItemParameterMapper;
    private StructureTestObjectService structureTestObjectService;
    private StructureTestObjectMapper structureTestObjectMapper;
    private LaboratoryMapper laboratoryMapper;
    private StandardTreeTemplateBindingMapper standardTreeTemplateBindingMapper;
    private StandardTemplateService standardTemplateService;
    @Override
    public List<FactoryDto> selectStandardTreeList() {
        List<FactoryDto> factoryDtos = standardTreeMapper.selectStandardTreeList();
        for (FactoryDto factoryDto : factoryDtos) {
            for (LaboratoryDto laboratoryDto : factoryDto.getChildren()) {
                laboratoryDto.getChildren().sort((o1, o2) -> (o1.getSort() == null ? 0 : o1.getSort())
                        - (o2.getSort() == null ? 0 : o2.getSort()));
                for (SampleTypeDto sampleTypeDto : laboratoryDto.getChildren()) {
//                    if (sampleTypeDto.getChildren().size() == 0) {
                    sampleTypeDto.getChildren().addAll(standardTreeMapper.getStandardTree3(sampleTypeDto.getValue()));
//                    }
                }
    public List<StandardLibraryTreeNodeDto> selectStandardTreeList() {
        List<StandardLibraryTreeRowDto> rows = standardTreeMapper.selectStandardLibraryTree();
        List<StandardLibraryTreeNodeDto> industries = new ArrayList<>();
        Map<Integer, StandardLibraryTreeNodeDto> industryMap = new LinkedHashMap<>();
        for (StandardLibraryTreeRowDto row : rows) {
            StandardLibraryTreeNodeDto industry = industryMap.get(row.getIndustryId());
            if (industry == null) {
                industry = new StandardLibraryTreeNodeDto();
                industry.setId(row.getIndustryId());
                industry.setLabel(row.getIndustryName());
                industry.setValue(row.getIndustryName());
                industry.setCode("[1]");
                industry.setChildren(new ArrayList<>());
                industryMap.put(row.getIndustryId(), industry);
                industries.add(industry);
            }
            if (row.getStandardId() != null) {
                StandardLibraryTreeNodeDto standard = new StandardLibraryTreeNodeDto();
                standard.setId(row.getStandardId());
                standard.setLabel(row.getStandardCode());
                standard.setValue(row.getStandardLabel());
                standard.setCode("[2]");
                standard.setSampleEn(row.getStandardNameEn());
                standard.setStandardMethodId(row.getStandardMethodId());
                standard.setStandardCode(row.getStandardCode());
                standard.setStandardName(row.getStandardName());
                standard.setChildren(null);
                industry.getChildren().add(standard);
            }
        }
        return factoryDtos;
        return industries;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addStandardTree(StandardTree standardTree) {
        LambdaQueryWrapper<StandardTree> wrapper = Wrappers.<StandardTree>lambdaQuery()
                .eq(StandardTree::getFactory, standardTree.getFactory())
                .eq(StandardTree::getLaboratory, standardTree.getLaboratory())
                .eq(StandardTree::getSampleType, standardTree.getSampleType())
                .eq(StandardTree::getSample, standardTree.getSample())
                .eq(StandardTree::getModel, standardTree.getModel());
        if (StringUtils.isNotBlank(standardTree.getSample())) {
            wrapper.eq(StandardTree::getSample, standardTree.getSample());
    public int addStandardTree(AddStandardTreeNodeDto dto) {
        if (dto == null) {
            throw new BaseException("标准树路径格式错误");
        }
        StandardTree tree = standardTreeMapper.selectOne(wrapper);
        if (tree != null) {
            throw new BaseException("该型号已存在");
        String name = StandardTreePathParser.normalizeNodeName(dto.getName());
        if (StringUtils.isBlank(dto.getParentTree())) {
            // æ–°å¢žè¡Œä¸šèŠ‚ç‚¹
            Long count = laboratoryMapper.selectCount(Wrappers.<Laboratory>lambdaQuery()
                    .eq(Laboratory::getLaboratoryName, name));
            if (count > 0) {
                throw new BaseException("行业名称已存在");
            }
            Laboratory laboratory = new Laboratory();
            laboratory.setLaboratoryName(name);
            laboratory.setSort(0);
            return laboratoryMapper.insert(laboratory);
        }
        return standardTreeMapper.insert(standardTree);
        throw new BaseException("二级标准请在“标准维护”中通过行业绑定进行维护");
        /*
        // åœ¨è¡Œä¸šä¸‹æ–°å¢žæ ‡å‡†èŠ‚ç‚¹
        StandardTreePath path = StandardTreePathParser.parse(dto.getParentTree());
        if (path.getStandard() != null) {
            throw new BaseException("标准节点下不能继续新增");
        }
        String industry = path.getIndustry();
        Long labCount = laboratoryMapper.selectCount(Wrappers.<Laboratory>lambdaQuery()
                .eq(Laboratory::getLaboratoryName, industry));
        if (labCount == 0) {
            throw new BaseException("行业不存在");
        }
        Long stdCount = standardTreeMapper.selectCount(Wrappers.<StandardTree>lambdaQuery()
                .eq(StandardTree::getLaboratory, industry)
                .eq(StandardTree::getSample, name)
                .isNull(StandardTree::getSampleType)
                .isNull(StandardTree::getModel));
        if (stdCount > 0) {
            throw new BaseException("该行业下标准名称已存在");
        }
        StandardTree tree = new StandardTree();
        tree.setLaboratory(industry);
        tree.setSample(name);
        tree.setFactory(null);
        tree.setSampleType(null);
        tree.setModel(null);
        tree.setSort(0);
        return standardTreeMapper.insert(tree);
        */
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delStandardTree(String tree) {
        String[] trees = tree.split(" - ");
        switch (trees.length) {
            case 5:
                if (trees[3].equals("null")) {
                    standardTreeMapper.delete(Wrappers.<StandardTree>lambdaUpdate().eq(StandardTree::getFactory, trees[0]).eq(StandardTree::getLaboratory, trees[1]).eq(StandardTree::getSampleType, trees[2]).isNull(StandardTree::getSample).eq(StandardTree::getModel, trees[4]));
                } else {
                    standardTreeMapper.delete(Wrappers.<StandardTree>lambdaUpdate().eq(StandardTree::getFactory, trees[0]).eq(StandardTree::getLaboratory, trees[1]).eq(StandardTree::getSampleType, trees[2]).eq(StandardTree::getSample, trees[3]).eq(StandardTree::getModel, trees[4]));
                }
                break;
        StandardTreePath path = StandardTreePathParser.parse(tree);
        String industry = path.getIndustry();
        String standard = path.getStandard();
        if (standard != null) {
            throw new BaseException("二级标准请在“标准维护”中通过行业绑定进行维护");
        }
        if (standard == null) {
            // åˆ é™¤è¡Œä¸šï¼šè¡Œä¸šä¸‹ä»æœ‰æ ‡å‡†æ—¶æ‹’绝删除
            Long count = standardTreeMapper.selectCount(Wrappers.<StandardTree>lambdaQuery()
                    .eq(StandardTree::getLaboratory, industry)
                    .isNull(StandardTree::getSampleType)
                    .isNull(StandardTree::getModel));
            if (count > 0) {
                throw new BaseException("该行业下仍有标准,请先删除标准");
            }
            Laboratory laboratory = laboratoryMapper.selectOne(Wrappers.<Laboratory>lambdaQuery()
                    .eq(Laboratory::getLaboratoryName, industry));
            if (laboratory == null) {
                throw new BaseException("行业不存在");
            }
            Long objectCount = structureTestObjectMapper.selectCount(Wrappers.<StructureTestObject>lambdaQuery()
                    .eq(StructureTestObject::getLaboratoryId, laboratory.getId()));
            if (objectCount > 0) {
                throw new BaseException("该行业已绑定检测对象,禁止删除");
            }
            int deleted = laboratoryMapper.delete(Wrappers.<Laboratory>lambdaQuery()
                    .eq(Laboratory::getLaboratoryName, industry));
            if (deleted == 0) {
                throw new BaseException("行业不存在");
            }
            return deleted;
        }
        // åˆ é™¤æ ‡å‡†åŠå…¶æ ‡å‡†åº“配置(不可达,保留历史实现说明)
        StandardTree standardTree = standardTreeMapper.selectOne(Wrappers.<StandardTree>lambdaQuery()
                .eq(StandardTree::getLaboratory, industry)
                .eq(StandardTree::getSample, standard)
                .isNull(StandardTree::getSampleType)
                .isNull(StandardTree::getModel));
        if (standardTree == null) {
            throw new BaseException("标准不存在");
        }
        standardTreeTemplateBindingMapper.delete(Wrappers.<StandardTreeTemplateBinding>lambdaQuery()
                .eq(StandardTreeTemplateBinding::getStandardTreeId, standardTree.getId()));
        standardTreeMapper.deleteById(standardTree.getId());
        standardProductListMapper.delete(Wrappers.<StandardProductList>lambdaQuery()
                .eq(StandardProductList::getLaboratory, industry)
                .eq(StandardProductList::getSample, standard)
                .isNull(StandardProductList::getSampleType)
                .isNull(StandardProductList::getModel));
        return 1;
    }
@@ -125,82 +236,225 @@
    }
    @Override
    public boolean updateTreeSort(List<FactoryDto> list) {
        List<StructureTestObject> testObjects = new ArrayList<>();
        for (FactoryDto factoryDto : list) {
            for (LaboratoryDto laboratoryDto : factoryDto.getChildren()) {
                List<SampleTypeDto> children = laboratoryDto.getChildren();
                int sort = 0;
                // å¾ªçŽ¯ç¬¬ä¸‰å±‚
                for (SampleTypeDto child : children) {
                    StructureTestObject structureTestObject = new StructureTestObject();
                    structureTestObject.setSort(sort);
                    structureTestObject.setId(child.getSampleTypeId());
                    testObjects.add(structureTestObject);
                    sort++;
    @Transactional(rollbackFor = Exception.class)
    public boolean updateTreeSort(List<StandardLibraryTreeNodeDto> list) {
        List<StandardTree> trees = new ArrayList<>();
        int industrySort = 0;
        for (StandardLibraryTreeNodeDto industry : list) {
            Laboratory lab = new Laboratory();
            lab.setId(industry.getId());
            lab.setSort(industrySort);
            laboratoryMapper.updateById(lab);
            industrySort++;
            List<StandardLibraryTreeNodeDto> children = industry.getChildren();
            if (children != null) {
                int standardSort = 0;
                for (StandardLibraryTreeNodeDto standard : children) {
                    StandardTree tree = new StandardTree();
                    tree.setId(standard.getId());
                    tree.setSort(standardSort);
                    trees.add(tree);
                    standardSort++;
                }
            }
        }
        structureTestObjectService.updateBatchById(testObjects);
        if (!trees.isEmpty()) {
            this.updateBatchById(trees);
        }
        return true;
    }
    /**
     * ä¿®æ”¹æ ‡å‡†æ•°
     * @param standardTree
     * @return
     * ä¿®æ”¹æ ‡å‡†æ ‘节点名
     */
    @Override
    public int updateStandardTree(StandardTree standardTree) {
        // ä¿®æ”¹åç§°åŒ¹é…çš„æ ‡å‡†æ ‘下的检验项目
        // æŸ¥è¯¢æ‰€æœ‰å¯¹è±¡+名称的树
        LambdaUpdateWrapper<StandardProductList> updateWrapper = Wrappers.<StandardProductList>lambdaUpdate()
                .eq(StandardProductList::getFactory, standardTree.getFactory())
                .eq(StandardProductList::getLaboratory, standardTree.getLaboratory())
                .eq(StandardProductList::getSampleType, standardTree.getSampleType())
                .eq(StandardProductList::getModel, standardTree.getOldModel());
        if (StringUtils.isNotBlank(standardTree.getSample()) && !standardTree.getSample().equals("null")) {
            updateWrapper.eq(StandardProductList::getSample, standardTree.getSample());
        } else {
            updateWrapper.isNull(StandardProductList::getSample);
    @Transactional(rollbackFor = Exception.class)
    public int updateStandardTree(UpdateStandardTreeNodeDto dto) {
        if (dto == null) {
            throw new BaseException("标准树路径格式错误");
        }
        List<StandardProductList> standardProductLists = standardProductListService.list(updateWrapper);
        if (CollectionUtils.isNotEmpty(standardProductLists)) {
            for (StandardProductList standardProductList : standardProductLists) {
                // ä¿®æ”¹æ ·å“åç§°
                standardProductList.setModel(standardTree.getModel());
                // ä¿®æ”¹æ ‘名称
                // éœ€è¦æˆªå–第四级, é¿å…ä¸‰å››çº§åç§°ä¸€æ ·ä¿®æ”¹é”™è¯¯
                String[] trees = standardProductList.getTree().split(" - ");
                trees[4] = standardTree.getModel();
                List<String> list = CollUtil.newArrayList(trees);
                String newName = CollUtil.join(list, " - ");
                standardProductList.setTree(newName);
            }
            standardProductListService.updateBatchById(standardProductLists);
        String name = StandardTreePathParser.normalizeNodeName(dto.getName());
        String oldLabel = StandardTreePathParser.normalizeNodeName(dto.getOldLabel());
        if (StringUtils.isBlank(dto.getParentTree())) {
            return updateIndustry(oldLabel, name);
        }
        // ä¿®æ”¹æ ‡å‡†æ•°æ£€éªŒé¡¹ç›®
        LambdaUpdateWrapper<StandardTree> wrapper = Wrappers.<StandardTree>lambdaUpdate()
                .eq(StandardTree::getFactory, standardTree.getFactory())
                .eq(StandardTree::getLaboratory, standardTree.getLaboratory())
                .eq(StandardTree::getSampleType, standardTree.getSampleType())
                .eq(StandardTree::getModel, standardTree.getOldModel())
                .set(StandardTree::getModel, standardTree.getModel());
        if (StringUtils.isNotBlank(standardTree.getSample()) && !standardTree.getSample().equals("null")) {
            wrapper.eq(StandardTree::getSample, standardTree.getSample());
        } else {
            wrapper.isNull(StandardTree::getSample);
        }
        return standardTreeMapper.update(null, wrapper);
        throw new BaseException("二级标准请在“标准维护”中通过行业绑定进行维护");
    }
    private int updateIndustry(String oldName, String newName) {
        if (Objects.equals(oldName, newName)) {
            return ensureIndustryExists(oldName);
        }
        Long count = laboratoryMapper.selectCount(Wrappers.<Laboratory>lambdaQuery()
                .eq(Laboratory::getLaboratoryName, newName));
        if (count > 0) {
            throw new BaseException("行业名称已存在");
        }
        Laboratory old = laboratoryMapper.selectOne(Wrappers.<Laboratory>lambdaQuery()
                .eq(Laboratory::getLaboratoryName, oldName));
        if (old == null) {
            throw new BaseException("行业不存在");
        }
        Laboratory update = new Laboratory();
        update.setId(old.getId());
        update.setLaboratoryName(newName);
        if (laboratoryMapper.updateById(update) == 0) {
            throw new BaseException("行业不存在");
        }
        // çº§è”标准树规范化记录
        standardTreeMapper.update(null, Wrappers.<StandardTree>lambdaUpdate()
                .eq(StandardTree::getLaboratory, oldName)
                .isNull(StandardTree::getSampleType)
                .isNull(StandardTree::getModel)
                .set(StandardTree::getLaboratory, newName));
        // çº§è”标准库配置
        standardTreeMapper.updateProductListIndustry(oldName, newName);
        // çº§è”基础检验项
        structureItemParameterMapper.update(null, Wrappers.<StructureItemParameter>lambdaUpdate()
                .eq(StructureItemParameter::getLaboratory, oldName)
                .set(StructureItemParameter::getLaboratory, newName));
        // çº§è”未完成订单
        standardTreeMapper.updateInsOrderIndustry(oldName, newName);
        standardTreeMapper.updateInsSampleIndustry(oldName, newName);
        standardTreeMapper.updateInsProductIndustry(oldName, newName);
        return 1;
    }
    private int updateStandard(String industry, String oldName, String newName) {
        if (Objects.equals(oldName, newName)) {
            return ensureStandardExists(industry, oldName);
        }
        Long count = standardTreeMapper.selectCount(Wrappers.<StandardTree>lambdaQuery()
                .eq(StandardTree::getLaboratory, industry)
                .eq(StandardTree::getSample, newName)
                .isNull(StandardTree::getSampleType)
                .isNull(StandardTree::getModel));
        if (count > 0) {
            throw new BaseException("该行业下标准名称已存在");
        }
        StandardTree old = standardTreeMapper.selectOne(Wrappers.<StandardTree>lambdaQuery()
                .eq(StandardTree::getLaboratory, industry)
                .eq(StandardTree::getSample, oldName)
                .isNull(StandardTree::getSampleType)
                .isNull(StandardTree::getModel));
        if (old == null) {
            throw new BaseException("标准不存在");
        }
        StandardTree update = new StandardTree();
        update.setId(old.getId());
        update.setSample(newName);
        if (standardTreeMapper.updateById(update) == 0) {
            throw new BaseException("标准不存在");
        }
        // çº§è”标准库配置
        standardTreeMapper.updateProductListStandard(industry, oldName, newName);
        // çº§è”未完成订单
        standardTreeMapper.updateInsOrderStandard(industry, oldName, newName);
        standardTreeMapper.updateInsSampleStandard(industry, oldName, newName);
        standardTreeMapper.updateInsProductStandard(industry, oldName, newName);
        return 1;
    }
    private int ensureIndustryExists(String industry) {
        Long count = laboratoryMapper.selectCount(Wrappers.<Laboratory>lambdaQuery()
                .eq(Laboratory::getLaboratoryName, industry));
        if (count == 0) {
            throw new BaseException("行业不存在");
        }
        return 1;
    }
    private int ensureStandardExists(String industry, String standard) {
        Long count = standardTreeMapper.selectCount(Wrappers.<StandardTree>lambdaQuery()
                .eq(StandardTree::getLaboratory, industry)
                .eq(StandardTree::getSample, standard)
                .isNull(StandardTree::getSampleType)
                .isNull(StandardTree::getModel));
        if (count == 0) {
            throw new BaseException("标准不存在");
        }
        return 1;
    }
    @Override
    public List<StandardTreeTemplateBindingVo> selectTemplateBindings(Integer standardTreeId) {
        ensureTwoLevelStandard(standardTreeId);
        List<StandardTreeTemplateBinding> bindings = standardTreeTemplateBindingMapper.selectList(
                Wrappers.<StandardTreeTemplateBinding>lambdaQuery()
                        .eq(StandardTreeTemplateBinding::getStandardTreeId, standardTreeId)
                        .orderByAsc(StandardTreeTemplateBinding::getSort, StandardTreeTemplateBinding::getId));
        List<StandardTreeTemplateBindingVo> result = new ArrayList<>();
        for (StandardTreeTemplateBinding binding : bindings) {
            StandardTemplate template = standardTemplateService.getById(binding.getTemplateId());
            if (template == null) {
                continue;
            }
            StandardTreeTemplateBindingVo vo = new StandardTreeTemplateBindingVo();
            vo.setTemplateId(template.getId());
            vo.setName(template.getName());
            vo.setNumber(template.getNumber());
            vo.setRemark(template.getRemark());
            vo.setSort(binding.getSort());
            result.add(vo);
        }
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveTemplateBindings(StandardTreeTemplateBindingDto dto) {
        if (dto == null || dto.getStandardTreeId() == null) {
            throw new BaseException("标准不能为空");
        }
        ensureTwoLevelStandard(dto.getStandardTreeId());
        List<TemplateBindingItemDto> templates = dto.getTemplates() == null ? new ArrayList<>() : dto.getTemplates();
        Set<Integer> templateIds = new HashSet<>();
        List<StandardTreeTemplateBinding> bindings = new ArrayList<>();
        int index = 0;
        for (TemplateBindingItemDto item : templates) {
            if (item == null || item.getTemplateId() == null || !templateIds.add(item.getTemplateId())) {
                throw new BaseException("原始记录模板不能重复绑定");
            }
            StandardTemplate template = standardTemplateService.getById(item.getTemplateId());
            if (template == null) {
                throw new BaseException("原始记录模板不存在");
            }
            StandardTreeTemplateBinding binding = new StandardTreeTemplateBinding();
            binding.setStandardTreeId(dto.getStandardTreeId());
            binding.setTemplateId(item.getTemplateId());
            binding.setSort(item.getSort() == null ? index : item.getSort());
            bindings.add(binding);
            index++;
        }
        standardTreeTemplateBindingMapper.delete(Wrappers.<StandardTreeTemplateBinding>lambdaQuery()
                .eq(StandardTreeTemplateBinding::getStandardTreeId, dto.getStandardTreeId()));
        for (StandardTreeTemplateBinding binding : bindings) {
            standardTreeTemplateBindingMapper.insert(binding);
        }
        return true;
    }
    @Override
    public List<TemplateInspectionItemDto> selectTemplateInspectionItems(Integer standardTreeId) {
        List<StandardTreeTemplateBindingVo> bindings = selectTemplateBindings(standardTreeId);
        List<TemplateInspectionItemDto> result = new ArrayList<>();
        for (StandardTreeTemplateBindingVo binding : bindings) {
            List<TemplateInspectionItemDto> items = StandardTemplateInspectionItemParser.parse(binding.getTemplateId(),
                    binding.getSort(), standardTemplateService.getStandTempThingById(binding.getTemplateId()));
            result.addAll(items);
        }
        result.sort(Comparator.comparing(TemplateInspectionItemDto::getTemplateSort)
                .thenComparing(TemplateInspectionItemDto::getTemplateRowIndex));
        return result;
    }
    private StandardTree ensureTwoLevelStandard(Integer standardTreeId) {
        StandardTree standardTree = this.getById(standardTreeId);
        if (standardTree == null || StringUtils.isBlank(standardTree.getSample())
                || standardTree.getSampleType() != null || standardTree.getModel() != null) {
            throw new BaseException("请选择标准库二级标准");
        }
        return standardTree;
    }
}
basic-server/src/main/java/com/ruoyi/basic/utils/StandardTemplateInspectionItemParser.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package com.ruoyi.basic.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.basic.dto.TemplateInspectionItemDto;
import com.ruoyi.common.exception.base.BaseException;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** è§£æž Luckysheet åŽŸå§‹è®°å½•æ¨¡æ¿ä¸­çš„â€œæ£€éªŒé¡¹â€æ ‡è®°ã€‚ */
public final class StandardTemplateInspectionItemParser {
    private static final String INSPECTION_ITEM_MARK = "检验项";
    private StandardTemplateInspectionItemParser() {
    }
    public static List<TemplateInspectionItemDto> parse(Integer templateId, Integer templateSort, String thing) {
        if (!StringUtils.hasText(thing)) {
            throw new BaseException("原始记录模板内容不能为空");
        }
        JSONArray sheets;
        try {
            JSONObject root = JSON.parseObject(thing);
            sheets = root == null ? null : root.getJSONArray("data");
            if (sheets == null) {
                sheets = JSON.parseArray(thing);
            }
        } catch (Exception e) {
            throw new BaseException("原始记录模板格式错误");
        }
        List<TemplateInspectionItemDto> result = new ArrayList<>();
        Set<Integer> rows = new HashSet<>();
        if (sheets == null) {
            throw new BaseException("原始记录模板格式错误");
        }
        for (Object sheetObject : sheets) {
            JSONObject sheet = (JSONObject) sheetObject;
            JSONArray cells = sheet.getJSONArray("celldata");
            if (cells == null) {
                continue;
            }
            for (Object cellObject : cells) {
                JSONObject cell = (JSONObject) cellObject;
                JSONObject value = cell.getJSONObject("v");
                if (value == null || !isInspectionItem(value)) {
                    continue;
                }
                Integer row = cell.getInteger("r");
                String item = value.getString("v");
                if (!StringUtils.hasText(item)) {
                    item = value.getString("m");
                }
                if (row == null || !StringUtils.hasText(item)) {
                    throw new BaseException("“检验项”标记所在单元格必须填写检验项名称");
                }
                if (!rows.add(row)) {
                    throw new BaseException("同一模板行只能配置一个“检验项”标记");
                }
                TemplateInspectionItemDto dto = new TemplateInspectionItemDto();
                dto.setTemplateId(templateId);
                dto.setTemplateSort(templateSort);
                dto.setTemplateRowIndex(row);
                dto.setInspectionItem(item.trim());
                result.add(dto);
            }
        }
        result.sort(Comparator.comparing(TemplateInspectionItemDto::getTemplateRowIndex));
        return result;
    }
    private static boolean isInspectionItem(JSONObject value) {
        JSONObject ps = value.getJSONObject("ps");
        return ps != null && INSPECTION_ITEM_MARK.equals(ps.getString("value"));
    }
}
basic-server/src/main/java/com/ruoyi/basic/utils/StandardTreePath.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.basic.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * æ ‡å‡†æ ‘路径解析结果
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StandardTreePath {
    private String industry;
    private String standard;
}
basic-server/src/main/java/com/ruoyi/basic/utils/StandardTreePathParser.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
package com.ruoyi.basic.utils;
import com.ruoyi.common.exception.base.BaseException;
import java.util.regex.Pattern;
/**
 * æ ‡å‡†æ ‘固定两级路径解析工具。
 *
 * å…¼å®¹å‰ç«¯ä¼ é€’的两种叶子路径:
 * æ ‡å‡†åº“侧:行业 - null - æ ‡å‡†
 * ä¸‹å•侧:行业 -  - æ ‡å‡†
 * ç»Ÿä¸€è§£æžä¸º industry = ç¬¬ä¸€ä¸ªæœ‰æ•ˆæ®µï¼Œstandard = æœ€åŽä¸€ä¸ªæœ‰æ•ˆæ®µã€‚
 */
public final class StandardTreePathParser {
    private static final String SEPARATOR = " - ";
    private static final String NULL_MARKER = "null";
    private StandardTreePathParser() {
    }
    /**
     * è§£æžè·¯å¾„为行业 + æ ‡å‡†ã€‚标准可能为 null(表示仅行业节点)。
     */
    public static StandardTreePath parse(String tree) {
        if (tree == null || tree.trim().isEmpty()) {
            throw new BaseException("标准树路径格式错误");
        }
        String[] parts = tree.split(Pattern.quote(SEPARATOR), -1);
        for (int i = 0; i < parts.length; i++) {
            parts[i] = parts[i].trim();
        }
        if (parts.length == 1) {
            validateSegment(parts[0]);
            return new StandardTreePath(parts[0], null);
        }
        if (parts.length == 2) {
            validateSegment(parts[0]);
            validateSegment(parts[1]);
            return new StandardTreePath(parts[0], parts[1]);
        }
        if (parts.length == 3 && (parts[1].isEmpty() || NULL_MARKER.equals(parts[1]))) {
            validateSegment(parts[0]);
            validateSegment(parts[2]);
            return new StandardTreePath(parts[0], parts[2]);
        }
        throw new BaseException("标准树路径格式错误");
    }
    /**
     * ç”Ÿæˆè§„范路径:行业 - null - æ ‡å‡†
     */
    public static String canonical(String industry, String standard) {
        String normalizedIndustry = trim(industry);
        String normalizedStandard = trim(standard);
        validateSegment(normalizedIndustry);
        validateSegment(normalizedStandard);
        return normalizedIndustry + SEPARATOR + NULL_MARKER + SEPARATOR + normalizedStandard;
    }
    private static String trim(String value) {
        return value == null ? "" : value.trim();
    }
    public static String normalizeNodeName(String value) {
        String normalized = trim(value);
        validateSegment(normalized);
        return normalized;
    }
    private static void validateSegment(String value) {
        if (value == null || value.isEmpty() || NULL_MARKER.equals(value) || value.contains(SEPARATOR)) {
            throw new BaseException("标准树路径格式错误");
        }
    }
}
basic-server/src/main/resources/mapper/StandardMethodMapper.xml
@@ -21,7 +21,6 @@
                sm.code,
                sm.name,
                sm.structure_test_object_id,
                sto.specimen_name sample_type,
                sm.remark,
                u1.name create_user_name,
                sm.create_time,
@@ -31,11 +30,14 @@
                sm.field,
                sm.name_en,
                sm.is_use,
                sm.qualification_id
                sm.qualification_id,
                COALESCE((SELECT GROUP_CONCAT(l.laboratory_name ORDER BY COALESCE(l.sort, 0), l.id SEPARATOR '、')
                          FROM standard_method_industry smi JOIN laboratory l ON l.id = smi.industry_id
                          WHERE smi.standard_method_id = sm.id), '') industry_names,
                (SELECT COUNT(*) FROM standard_method_attachment sma WHERE sma.standard_method_id = sm.id) attachment_count
                from standard_method sm
                left join user u1 on sm.create_user = u1.id
                left join user u2 on sm.update_user = u2.id
                left join structure_test_object sto on sm.structure_test_object_id = sto.id
                where sm.code != '技术要求'
                ) a
        <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
@@ -80,6 +82,16 @@
          and is_product = 1
          and structure_test_object_id LIKE CONCAT('%', #{tree}, '%')
    </select>
    <select id="selectStandardMethodListsByIndustry" resultType="com.ruoyi.basic.pojo.StandardMethodList">
        select sm.id, sm.code, sm.name, sm.remark
        from standard_method sm
                 inner join standard_method_industry smi on smi.standard_method_id = sm.id
                 inner join laboratory l on l.id = smi.industry_id
        where sm.is_use = 1
          and sm.is_product = 1
          and l.laboratory_name = #{industry}
        order by sm.id
    </select>
    <select id="selectListEnum" resultType="com.ruoyi.basic.pojo.StandardMethodList">
        select id,code,name from standard_method
        where is_product = 1
basic-server/src/main/resources/mapper/StandardTreeMapper.xml
@@ -10,6 +10,8 @@
        <result property="sampleType" column="sample_type" jdbcType="VARCHAR"/>
        <result property="sample" column="sample" jdbcType="VARCHAR"/>
        <result property="model" column="model" jdbcType="VARCHAR"/>
        <result property="sort" column="sort" jdbcType="INTEGER"/>
        <result property="standardMethodId" column="standard_method_id" jdbcType="INTEGER"/>
        <result property="createUser" column="create_user" jdbcType="INTEGER"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
        <result property="updateUser" column="update_user" jdbcType="INTEGER"/>
@@ -89,24 +91,106 @@
        rates
    </sql>
    <select id="selectStandardTreeList" resultMap="FactoryDto">
        select ''                          factory,
               l.laboratory_name laboratory,
               sto.specimen_name sample_type,
               p.name            sample,
               st.model,
               sto.code,
               sto.id  sample_type_id,
               sto.sort,
               sto.specimen_name_en sample_type_en,
               p.name_en sample_en
        from laboratory l
                 left join structure_test_object sto on sto.laboratory_id = l.id
                 left join product p on p.object_id = sto.id
                 left join standard_tree st on st.sample_type = sto.specimen_name
            and st.sample = p.name
        order by l.id, CAST(sto.code AS DECIMAL), p.id, ISNULL(st.id), st.id
    <select id="selectStandardLibraryTree" resultType="com.ruoyi.basic.dto.StandardLibraryTreeRowDto">
        SELECT l.id                                      AS industry_id,
               l.laboratory_name                         AS industry_name,
               l.sort                                    AS industry_sort,
               st.id                                     AS standard_id,
               sm.id                                     AS standard_method_id,
               sm.code                                   AS standard_code,
               sm.name                                   AS standard_name,
               sm.name_en                                AS standard_name_en,
               CONCAT(sm.code, ' - ', sm.name)           AS standard_label,
               st.sort                                   AS standard_sort
        FROM laboratory l
        LEFT JOIN standard_tree st
               ON st.laboratory = l.laboratory_name
              AND st.sample_type IS NULL
              AND st.model IS NULL
              AND st.standard_method_id IS NOT NULL
              AND EXISTS (SELECT 1 FROM standard_method sm0
                          WHERE sm0.id = st.standard_method_id
                            AND sm0.is_product = 1 AND sm0.is_use = 1)
        LEFT JOIN standard_method sm ON sm.id = st.standard_method_id
        ORDER BY COALESCE(l.sort, 0), l.id,
                 COALESCE(st.sort, 0), st.id
    </select>
    <update id="updateProductListIndustry">
        UPDATE standard_product_list
        SET laboratory = #{newName},
            tree = CASE WHEN sample_type IS NULL AND model IS NULL AND sample IS NOT NULL
                        THEN CONCAT(#{newName}, ' - null - ', sample)
                        ELSE tree END
        WHERE laboratory = #{oldName}
    </update>
    <update id="updateProductListStandard">
        UPDATE standard_product_list
        SET sample = #{newName},
            tree = CONCAT(#{industry}, ' - null - ', #{newName})
        WHERE laboratory = #{industry}
          AND sample = #{oldName}
          AND sample_type IS NULL
          AND model IS NULL
    </update>
    <update id="updateInsOrderIndustry">
        UPDATE ins_order
        SET laboratory = #{newName}
        WHERE laboratory = #{oldName}
          AND (state IS NULL OR state NOT IN (3, -1))
          AND (ins_state IS NULL OR ins_state != 5)
    </update>
    <update id="updateInsSampleIndustry">
        UPDATE ins_sample s
        JOIN ins_order o ON o.id = s.ins_order_id
        SET s.laboratory = #{newName}
        WHERE s.laboratory = #{oldName}
          AND (o.state IS NULL OR o.state NOT IN (3, -1))
          AND (o.ins_state IS NULL OR o.ins_state != 5)
    </update>
    <update id="updateInsProductIndustry">
        UPDATE ins_product p
        JOIN ins_sample s ON s.id = p.ins_sample_id
        JOIN ins_order o ON o.id = s.ins_order_id
        SET p.laboratory = #{newName}
        WHERE p.laboratory = #{oldName}
          AND (o.state IS NULL OR o.state NOT IN (3, -1))
          AND (o.ins_state IS NULL OR o.ins_state != 5)
    </update>
    <update id="updateInsOrderStandard">
        UPDATE ins_order
        SET sample = #{newName}
        WHERE laboratory = #{industry}
          AND sample = #{oldName}
          AND (state IS NULL OR state NOT IN (3, -1))
          AND (ins_state IS NULL OR ins_state != 5)
    </update>
    <update id="updateInsSampleStandard">
        UPDATE ins_sample s
        JOIN ins_order o ON o.id = s.ins_order_id
        SET s.sample = #{newName}
        WHERE s.laboratory = #{industry}
          AND s.sample = #{oldName}
          AND (o.state IS NULL OR o.state NOT IN (3, -1))
          AND (o.ins_state IS NULL OR o.ins_state != 5)
    </update>
    <update id="updateInsProductStandard">
        UPDATE ins_product p
        JOIN ins_sample s ON s.id = p.ins_sample_id
        JOIN ins_order o ON o.id = s.ins_order_id
        SET p.sample = #{newName}
        WHERE p.laboratory = #{industry}
          AND p.sample = #{oldName}
          AND (o.state IS NULL OR o.state NOT IN (3, -1))
          AND (o.ins_state IS NULL OR o.ins_state != 5)
    </update>
    <select id="selectStandardProductById" resultType="com.ruoyi.basic.pojo.StandardProductList">
        select inspection_item,
@@ -194,6 +278,44 @@
        order by inspection_item_class, inspection_item, id asc
    </select>
    <select id="selectStandardProductListByIndustry" resultType="com.ruoyi.basic.pojo.StandardProductList">
        select inspection_item,
               inspection_item_en,
               inspection_item_subclass,
               inspection_item_subclass_en,
               inspection_item_class,
               inspection_item_class_en,
               method,
               son_laboratory,
               unit,
               ask_tell as tell,
               ask,
               price,
               man_hour,
               man_hour_group,
               man_day,
               inspection_item_type,
               inspection_value_type,
               bsm,
               template_id,
               laboratory,
               checkout_number,
               section,
               dic,
               0 as state,
               null as model,
               #{standard} as sample,
               #{tree} as tree,
               id as structure_item_parameter_id,
               radius_list,
               rates
        from structure_item_parameter
        where (laboratory is null
            or laboratory = ''
            or laboratory = #{industry})
        order by inspection_item_class, inspection_item, id asc
    </select>
    <select id="getStandardTree2" resultMap="SampleTypeDto">
        select sto.specimen_name sample_type,
               p.name            sample,
cnas-require/src/main/java/com/ruoyi/require/service/impl/FeStandardSubstanceServiceImpl.java
@@ -4,7 +4,6 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
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.framework.exception.ErrorException;
@@ -23,11 +22,8 @@
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * <p>
@@ -85,26 +81,11 @@
            throw new ErrorException("导入文件没有标准物质数据");
        }
        Set<String> manageNumbers = new HashSet<>();
        for (int i = 0; i < rows.size(); i++) {
            String manageNum = required(rows.get(i).getManageNum(), i + 2, "管理编号");
            if (!manageNumbers.add(manageNum)) {
                throw new ErrorException("第" + (i + 2) + "行:管理编号" + manageNum + "在文件中重复");
            }
        }
        Set<String> existingNumbers = baseMapper.selectList(Wrappers.<FeStandardSubstance>lambdaQuery()
                        .in(FeStandardSubstance::getManageNum, manageNumbers)
                        .select(FeStandardSubstance::getManageNum))
                .stream().map(FeStandardSubstance::getManageNum).collect(Collectors.toSet());
        List<FeStandardSubstance> substances = new ArrayList<>();
        for (int i = 0; i < rows.size(); i++) {
            int rowNumber = i + 2;
            FeStandardSubstanceImport row = rows.get(i);
            String manageNum = required(row.getManageNum(), rowNumber, "管理编号");
            if (existingNumbers.contains(manageNum)) {
                throw new ErrorException("第" + rowNumber + "行:管理编号" + manageNum + "已存在");
            }
            FeStandardSubstance substance = new FeStandardSubstance();
            substance.setName(required(row.getName(), rowNumber, "标准物质名称"));
@@ -160,4 +141,4 @@
    private String trim(String value) {
        return value == null ? null : value.trim();
    }
}
}
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsOrderController.java
@@ -82,6 +82,9 @@
    @PreAuthorize("@ss.hasPermi('add:insOrder')")
    @PostMapping("/addInsOrder")
    public Result<?> addInsOrder(@RequestBody InsPlaceOrderDto insPlaceOrderDto) {
        if (insPlaceOrderDto.getStandardTreeId() != null) {
            return Result.success(insOrderService.addNewJuInsOrder(insPlaceOrderDto));
        }
        return Result.success(insOrderService.addInsOrder(insPlaceOrderDto.getSampleList(), insPlaceOrderDto.getInsOrder()));
    }
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsOrderPlanController.java
@@ -9,6 +9,10 @@
import com.ruoyi.inspect.dto.InsOrderPlanProductDto;
import com.ruoyi.inspect.dto.InsProductBindingDto;
import com.ruoyi.inspect.dto.SaveInsContextDto;
import com.ruoyi.inspect.dto.SaveOrderDevicesDto;
import com.ruoyi.inspect.dto.SaveTemplateContextDto;
import com.ruoyi.inspect.dto.VerifyPlanDto;
import com.ruoyi.inspect.dto.WriteInspectionConditionDto;
import com.ruoyi.inspect.pojo.InsOrder;
import com.ruoyi.inspect.pojo.InsOrderFactoryVerify;
import com.ruoyi.inspect.pojo.InsOrderFile;
@@ -93,21 +97,21 @@
    @ApiOperation(value = "复核检验任务")
    @PostMapping("/verifyPlan")
    public Result<?> verifyPlan(Integer orderId, String laboratory, Integer type, String tell, Integer userId) {
        return Result.success(insOrderPlanService.rawMaterialVerifyPlan(orderId, laboratory, type, tell, userId));
    public Result<?> verifyPlan(@RequestBody VerifyPlanDto verifyPlanDto) {
        return Result.success(insOrderPlanService.rawMaterialVerifyPlan(verifyPlanDto));
    }
    @ApiOperation(value = "校验检验任务提交")
    @PostMapping("/checkSubmitPlan")
    public Result<?> checkSubmitPlan(Integer orderId, String laboratory, Integer reportType) {
        return Result.success(insOrderPlanService.checkSubmitPlan(orderId, laboratory, reportType));
    public Result<?> checkSubmitPlan(Integer orderId, String laboratory) {
        return Result.success(insOrderPlanService.checkSubmitPlan(orderId, laboratory));
    }
    @ApiOperation(value = "检验任务提交(reportType:0检测报告,1检验报告)")
    @ApiOperation(value = "检验任务提交")
    @PostMapping("/submitPlan")
    public Result<?> submitPlan(Integer orderId, String laboratory, Integer verifyUser, String entrustCode, Boolean registerInsResults, Integer reportType) {
        int num = insOrderPlanService.submitPlan(orderId, laboratory, verifyUser, entrustCode, registerInsResults, reportType);
    public Result<?> submitPlan(Integer orderId, String laboratory, Integer verifyUser, String entrustCode, Boolean registerInsResults) {
        int num = insOrderPlanService.submitPlan(orderId, laboratory, verifyUser, entrustCode, registerInsResults);
        return num == 1 ? Result.success() : Result.fail("提交失败,部分项目还未进行检验");
    }
@@ -115,6 +119,26 @@
    @PostMapping("/saveInsContext")
    public Result<?> saveInsContext(@RequestBody SaveInsContextDto saveInsContextDto) {
        insOrderPlanService.saveInsContext(saveInsContextDto);
        return Result.success();
    }
    @ApiOperation(value = "按模板坐标保存新聚原始记录")
    @PostMapping("/saveTemplateContext")
    public Result<?> saveTemplateContext(@RequestBody SaveTemplateContextDto saveTemplateContextDto) {
        insOrderPlanService.saveTemplateContext(saveTemplateContextDto);
        return Result.success();
    }
    @ApiOperation(value = "查询检验单选择的设备")
    @GetMapping("/orderDevices")
    public Result<?> getOrderDevices(Integer orderId) {
        return Result.success(insOrderPlanService.getOrderDeviceIds(orderId));
    }
    @ApiOperation(value = "保存检验单选择的设备")
    @PutMapping("/orderDevices")
    public Result<?> saveOrderDevices(@RequestBody SaveOrderDevicesDto saveOrderDevicesDto) {
        insOrderPlanService.saveOrderDevices(saveOrderDevicesDto);
        return Result.success();
    }
@@ -153,8 +177,8 @@
    @ApiOperation(value = "填写温度与湿度")
    @PostMapping("/write")
    public Result<?> write(@RequestBody InsOrder insOrder) {
        return Result.success(insProductService.write(insOrder));
    public Result<?> write(@RequestBody WriteInspectionConditionDto dto) {
        return Result.success(insProductService.write(dto));
    }
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsReportController.java
@@ -150,6 +150,14 @@
        return Result.success(insReportService.downAll(ids));
    }
    @ApiOperation(value = "报告下载")
    @GetMapping("/download")
    @PreAuthorize("@ss.hasPermi('business:reportPreparation')")
    @PersonalScope(permsName = "business:reportPreparation", objectName = ReportPageDto.class, paramName = "createOrderUser")
    public void download(Integer id, ReportPageDto reportPageDto, HttpServletResponse response) {
        insReportService.download(id, reportPageDto.getCreateOrderUser(), response);
    }
    @ApiOperation(value = "报告批量上传")
    @PostMapping("/upAll")
    public Result upAll(MultipartFile file) throws IOException {
inspect-server/src/main/java/com/ruoyi/inspect/dto/InsPlaceOrderDto.java
@@ -21,6 +21,15 @@
    @ApiModelProperty("订单信息")
    private InsOrder insOrder;
    @ApiModelProperty("标准库二级标准ID(新聚单样品下单)")
    private Integer standardTreeId;
    @ApiModelProperty("样品编号(新聚单样品下单)")
    private String sampleCode;
    @ApiModelProperty("下单时保留的原始记录模板")
    private List<OrderTemplateSelectionDto> templateList;
    @ApiModelProperty("铜单丝订单")
    private CopperInsOrderDto copperInsOrder;
inspect-server/src/main/java/com/ruoyi/inspect/dto/OrderTemplateSelectionDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.inspect.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/** ä¸‹å•时保留的原始记录模板。 */
@Data
public class OrderTemplateSelectionDto {
    @ApiModelProperty("模板ID")
    private Integer templateId;
    @ApiModelProperty("模板展示及生成顺序")
    private Integer sort;
}
inspect-server/src/main/java/com/ruoyi/inspect/dto/SaveOrderDevicesDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.inspect.dto;
import lombok.Data;
import java.util.List;
/** æ£€éªŒå•概况中的设备选择。 */
@Data
public class SaveOrderDevicesDto {
    private Integer orderId;
    private List<Integer> deviceIds;
}
inspect-server/src/main/java/com/ruoyi/inspect/dto/SaveTemplateContextDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.inspect.dto;
import lombok.Data;
import java.util.Map;
/** æ–°èšåŽŸå§‹è®°å½•æ¨¡æ¿æŒ‰åæ ‡ä¿å­˜ã€‚ */
@Data
public class SaveTemplateContextDto {
    private Integer orderId;
    private Integer sampleId;
    private Integer templateId;
    private Map<String, Object> values;
}
inspect-server/src/main/java/com/ruoyi/inspect/dto/VerifyPlanDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.inspect.dto;
import lombok.Data;
/** æ£€éªŒå¤æ ¸æäº¤å‚数。 */
@Data
public class VerifyPlanDto {
    private Integer orderId;
    private String laboratory;
    /** 1 é€šè¿‡ï¼›0 é€€å›žã€‚ */
    private Integer type;
    private String tell;
    private Integer userId;
    /** æ–°èšæ£€éªŒå•整体结论:1 åˆæ ¼ï¼Œ0 ä¸åˆæ ¼ã€‚ */
    private Integer insResult;
}
inspect-server/src/main/java/com/ruoyi/inspect/dto/WriteInspectionConditionDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package com.ruoyi.inspect.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/** ä¿å­˜è®¢å•级备注或模板级检验条件。 */
@Data
public class WriteInspectionConditionDto {
    private Integer id;
    private Integer templateId;
    private String remark;
    private String temperature;
    private String humidity;
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate detectionTime;
    private String detectionPlace;
    private List<Integer> deviceIds;
    private Integer insResult;
    @JsonIgnore
    private boolean temperaturePresent;
    @JsonIgnore
    private boolean humidityPresent;
    @JsonIgnore
    private boolean detectionTimePresent;
    @JsonIgnore
    private boolean detectionPlacePresent;
    @JsonIgnore
    private boolean deviceIdsPresent;
    @JsonIgnore
    private boolean insResultPresent;
    public void setTemperature(String temperature) {
        this.temperature = temperature;
        this.temperaturePresent = true;
    }
    public void setHumidity(String humidity) {
        this.humidity = humidity;
        this.humidityPresent = true;
    }
    public void setDetectionTime(LocalDate detectionTime) {
        this.detectionTime = detectionTime;
        this.detectionTimePresent = true;
    }
    public void setDetectionPlace(String detectionPlace) {
        this.detectionPlace = detectionPlace;
        this.detectionPlacePresent = true;
    }
    public void setDeviceIds(List<Integer> deviceIds) {
        this.deviceIds = deviceIds;
        this.deviceIdsPresent = true;
    }
    public void setInsResult(Integer insResult) {
        this.insResult = insResult;
        this.insResultPresent = true;
    }
}
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderTemplateDeviceMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.inspect.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.inspect.pojo.InsOrderTemplateDevice;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface InsOrderTemplateDeviceMapper extends BaseMapper<InsOrderTemplateDevice> {
    @Select({"<script>",
            "select id from device where id in",
            "<foreach collection='deviceIds' item='deviceId' open='(' separator=',' close=')'>",
            "#{deviceId}",
            "</foreach>",
            "</script>"})
    List<Integer> selectExistingDeviceIds(@Param("deviceIds") List<Integer> deviceIds);
    @Select("select distinct d.management_number " +
            "from ins_order_template_device otd " +
            "join device d on d.id = otd.device_id " +
            "where otd.ins_order_id = #{orderId} " +
            "and d.management_number is not null and d.management_number <> '' " +
            "order by d.management_number")
    List<String> selectManagementNumbersByOrderId(@Param("orderId") Integer orderId);
}
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsReportMapper.java
@@ -34,6 +34,10 @@
    String getLaboratoryByName(@Param("name") String name, @Param("type") String type);
    /** æŒ‰æŠ¥å‘Šç¼–制列表相同的数据范围查询可下载的报告。 */
    InsReport selectDownloadableReport(@Param("id") Integer id,
                                       @Param("createOrderUser") Integer createOrderUser);
    /**
     * æŠ¥å‘ŠæŠ¥è¡¨å¯¼å‡º
     * @return
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrder.java
@@ -195,8 +195,25 @@
    @ApiModelProperty("湿度")
    private String humidity;
    @TableField(exist = false)
    @ApiModelProperty("当前填写环境条件的模板ID")
    private Integer templateId;
    @ApiModelProperty("样品状态")
    private String sampleStatus;
    @ApiModelProperty("送样日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate sampleDate;
    @ApiModelProperty("生产日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate productionDate;
    @ApiModelProperty("生产批号")
    private String productionBatch;
    @ApiModelProperty("季度抽样项目id")
    private Integer quarterItemId;
@@ -213,6 +230,9 @@
    @ApiModelProperty("第一次提交时间")
    private LocalDateTime firstSubmitDate;
    @ApiModelProperty("首次保存检验模板值时间")
    private LocalDateTime firstInspectDate;
    @ApiModelProperty("样机员(除了其他成品外这个字段必选,用于企业微信推送)")
    @TableField(exist = false)
    private Integer issueUser;
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderStandardTemplate.java
@@ -1,10 +1,12 @@
package com.ruoyi.inspect.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.util.Date;
/**
@@ -54,5 +56,27 @@
    @ApiModelProperty("模板编号")
    private String number;
    @ApiModelProperty("模板展示及生成顺序")
    private Integer sort;
    @ApiModelProperty("原始记录已填写单元格,按坐标保存")
    private String recordValues;
    @ApiModelProperty("该模板检验时的温度")
    private String temperature;
    @ApiModelProperty("该模板检验时的湿度")
    private String humidity;
    @ApiModelProperty("检测日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate detectionTime;
    @ApiModelProperty("检测地点")
    private String detectionPlace;
    @ApiModelProperty("模板检验结论:0不合格,1合格")
    private Integer insResult;
}
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderTemplateDevice.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.inspect.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/** è®¢å•模板与检验设备关系。 */
@Data
@TableName("ins_order_template_device")
public class InsOrderTemplateDevice {
    @TableId(type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("订单ID")
    private Integer insOrderId;
    @ApiModelProperty("模板ID")
    private Integer templateId;
    @ApiModelProperty("设备ID")
    private Integer deviceId;
    @ApiModelProperty("展示顺序")
    private Integer sort;
}
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsProduct.java
@@ -149,6 +149,9 @@
    @ApiModelProperty("外键:standard_template表id")
    private Integer templateId;
    @ApiModelProperty("原始记录模板中的检验项行号")
    private Integer templateRowIndex;
    @TableField(exist = false)
    @ApiModelProperty("模板内容")
    private List<JSONObject> template;
@@ -165,6 +168,14 @@
    @ApiModelProperty("模板名称")
    private String templateName;
    @TableField(exist = false)
    @ApiModelProperty("订单模板页签顺序")
    private Integer templateSort;
    @TableField(exist = false)
    @ApiModelProperty("订单模板已填写单元格,按坐标保存")
    private String recordValues;
    @ApiModelProperty("字典类型")
    private String dic;
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderPlanService.java
@@ -5,6 +5,9 @@
import com.ruoyi.inspect.dto.InsOrderPlanDTO;
import com.ruoyi.inspect.dto.InsOrderPlanProductDto;
import com.ruoyi.inspect.dto.SaveInsContextDto;
import com.ruoyi.inspect.dto.SaveOrderDevicesDto;
import com.ruoyi.inspect.dto.SaveTemplateContextDto;
import com.ruoyi.inspect.dto.VerifyPlanDto;
import com.ruoyi.inspect.pojo.InsOrderFactoryVerify;
import com.ruoyi.inspect.pojo.InsOrderFile;
import com.ruoyi.inspect.pojo.InsProduct;
@@ -28,15 +31,21 @@
    void saveInsContext(SaveInsContextDto saveInsContextDto);
    void saveTemplateContext(SaveTemplateContextDto saveTemplateContextDto);
    List<Integer> getOrderDeviceIds(Integer orderId);
    void saveOrderDevices(SaveOrderDevicesDto saveOrderDevicesDto);
    Map<String, Object> doInsOrder(Integer id, String laboratory);
    int upPlanUser(Integer userId, Integer orderId,String sonLaboratory);
    int submitPlan(Integer orderId, String laboratory, Integer verifyUser, String entrustCode, Boolean registerInsResults, Integer reportType);
    int submitPlan(Integer orderId, String laboratory, Integer verifyUser, String entrustCode, Boolean registerInsResults);
    List<InsProduct> getInsProduct(InsOrderPlanProductDto insOrderPlanProductDto);
    Map<String,Object> checkSubmitPlan(Integer orderId, String laboratory, Integer reportType);
    Map<String,Object> checkSubmitPlan(Integer orderId, String laboratory);
    IPage<InsOrderFile> getFileList(Page page, InsOrderFile insOrderFile);
@@ -47,6 +56,8 @@
    int rawMaterialVerifyPlan(Integer orderId, String laboratory, Integer type, String tell, Integer userId);
    int rawMaterialVerifyPlan(VerifyPlanDto verifyPlanDto);
    /**
     * ç”µç¼†é…ç½®. æŸ¥çœ‹æ ‡è¯†
     * @param id
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderService.java
@@ -35,6 +35,8 @@
    int addInsOrder(List<SampleProductDto> list, InsOrder insOrder);
    int addNewJuInsOrder(InsPlaceOrderDto insPlaceOrderDto);
    Map<String, Object> getInsOrder(Integer id);
    int upInsOrderOfState(InsOrder insOrder);
inspect-server/src/main/java/com/ruoyi/inspect/service/InsProductService.java
@@ -4,7 +4,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.inspect.dto.InsProductBindingDto;
import com.ruoyi.inspect.pojo.InsOrder;
import com.ruoyi.inspect.dto.WriteInspectionConditionDto;
import com.ruoyi.inspect.pojo.InsProduct;
import com.ruoyi.performance.pojo.AuxiliaryOutputWorkingHours;
@@ -22,7 +22,7 @@
    int updateInspected(Integer id,String ids);
    boolean write(InsOrder insOrder);
    boolean write(WriteInspectionConditionDto dto);
    IPage<InsProduct> selectNoProducts(Page page, InsProduct insProduct, Integer orderId, String ids);
inspect-server/src/main/java/com/ruoyi/inspect/service/InsReportService.java
@@ -41,6 +41,9 @@
    String downAll(String ids);
    /** å•份报告 Word ä¸‹è½½ï¼ŒcreateOrderUser ç”¨äºŽå¤ç”¨æŠ¥å‘Šç¼–制的数据范围。 */
    void download(Integer id, Integer createOrderUser, HttpServletResponse response);
    int upAll(MultipartFile file) throws IOException;
    /**
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java
@@ -82,6 +82,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
 * æ£€éªŒä»»åŠ¡-业务实现层
@@ -155,6 +156,8 @@
    @Resource
    private InsOrderDeviceRecordMapper insOrderDeviceRecordMapper;
    @Resource
    private InsOrderTemplateDeviceMapper insOrderTemplateDeviceMapper;
    @Resource
    private InsOrderDeviceRecordService insOrderDeviceRecordService;
    @Resource
    private ISysDictTypeService iSysDictTypeService;
@@ -222,14 +225,7 @@
    @Override
    public Map<String, Object> doInsOrder(Integer id, String laboratory) {
        InsOrder insOrder = new InsOrder();
        insOrder.setId(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);
@@ -268,60 +264,94 @@
                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, Integer reportType) {
    public Map<String,Object> checkSubmitPlan(Integer orderId, String laboratory) {
        Map<String, Object> map = new HashMap<>();
        List<String> collect = new ArrayList<>();
        if (Objects.equals(DETECTION_REPORT, reportType)) {
            map.put("errorMsg", collect);
        if (isDirectTemplateOrder(orderId)) {
            map.put("errorMsg", Collections.emptyList());
            map.put("missingExportItems", getMissingTemplateExportValues(orderId));
            map.put("unInsOrderCount", 0L);
            return map;
        }
        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;
            }
            return false;
        }).collect(Collectors.toList());
        if (productList.size() > 0) {
            collect = productList.stream().map(insProduct -> {
                return insProduct.getInspectionItem() + "-" + insProduct.getInspectionItemSubclass();
            }).collect(Collectors.toList());
        }
        //查询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;
    }
@@ -557,6 +587,254 @@
        });
    }
    /**
     * æ–°èšè®¢å•的外层 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("未找到当前订单的原始记录模板快照");
        }
        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()));
        List<String> exportValueCoordinates = getExportValueCoordinates(snapshot.getThing()).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()) {
            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;
    }
    /**
     * ç›´ç»‘模板订单只校验模板中标记为“导出值”的单元格,不使用检验项状态或 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(snapshot.getThing()).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);
@@ -630,39 +908,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 åœ¨é¦–次填写前允许为空;Collectors.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());
                }
            }
        }
@@ -686,6 +980,7 @@
                product.setStyle(style);
                product.setTemplateName(standardTemplateService.getStandTempNameById(product.getTemplateId()));
            }
            product.setRecordValues(recordValuesByTemplate.get(product.getTemplateId()));
        }
    }
@@ -775,10 +1070,8 @@
    @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("请选择报告类型");
        }
    public int submitPlan(Integer orderId, String laboratory, Integer verifyUser, String entrustCode, Boolean registerInsResults) {
        int reportType = DETECTION_REPORT;
        InsOrder order = insOrderMapper.selectOne(Wrappers.<InsOrder>lambdaQuery()
                .eq(InsOrder::getId, orderId)
                .last("FOR UPDATE"));
@@ -794,30 +1087,41 @@
        }
        // 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)) {
        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);
        }
@@ -883,20 +1187,16 @@
            }
        });
        // 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 (isInspectionReport && order.getQuarterItemId() != null) {
            // åˆ¤æ–­æ˜¯å¦æœ‰ä¸åˆæ ¼
@@ -904,7 +1204,7 @@
        }
        // 12.添加订单费用统计信息
        // 11.添加订单费用统计信息
        List<InsProduct> productList = insProductMapper.selectProductByOrderId(orderId);
        // åˆ é™¤åŽŸæœ¬è´¹ç”¨ä¿¡æ¯
        insOrderRatesService.remove(Wrappers.<InsOrderRates>lambdaQuery()
@@ -927,7 +1227,7 @@
        insOrderRatesService.saveBatch(orderRatesList);
        // 13.发送企业微信通知
        // 12.发送企业微信通知
        // æŸ¥è¯¢åŽŸææ–™
        IfsInventoryQuantity ifsInventoryQuantity = ifsInventoryQuantityMapper.selectById(order.getIfsInventoryId());
        // æŸ¥è¯¢æ ·å“ä¿¡æ¯
@@ -946,20 +1246,20 @@
            }
            //发送企业微信消息通知  æäº¤å¤æ ¸
            try {
                WxCpUtils.inform(sendUserAccount, message, null);
//                WxCpUtils.inform(sendUserAccount, message, null);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        // 14.ifs移库(原材料需要进行移库操作) --> æœ€åŽæ‰§è¡Œ,因为失败无法回滚
        // 13.ifs移库(原材料需要进行移库操作) --> æœ€åŽæ‰§è¡Œ,因为失败无法回滚
        if (ifsInventoryQuantity != null && isInspectionReport) {
            // ç™»è®°æ£€éªŒç»“æžœ
            // åˆ¤æ–­æ˜¯å¦æœ‰ä¸åˆæ ¼, æœ‰ä¸åˆæ ¼ä¸èƒ½ç§»åº“
            // todo: ifs移库
            insReportService.isRawMaterial(order,registerInsResults,false);
            // 15 åˆ¤æ–­å½“前样品是否为原材料, åŽŸææ–™éœ€è¦è¿›è¡Œæ•°æ®åˆ†æž, åˆ¤æ–­ä¹‹å‰10条数据同一个供应商, åŒä¸€ä¸ªæ£€éªŒé¡¹çš„偏差是否超过10%
            // 14 åˆ¤æ–­å½“前样品是否为原材料, åŽŸææ–™éœ€è¦è¿›è¡Œæ•°æ®åˆ†æž, åˆ¤æ–­ä¹‹å‰10条数据同一个供应商, åŒä¸€ä¸ªæ£€éªŒé¡¹çš„偏差是否超过10%
            // æŸ¥è¯¢ifs信息获取获取前10个供应商一样的, æ£€éªŒé¡¹ä¸€æ ·ä¿¡æ¯
            threadPoolTaskExecutor.execute(() -> {
                // æ·»åŠ åˆ†æžæ•°æ®
@@ -967,7 +1267,7 @@
            });
        } else if (isInspectionReport) {
        } else if (isInspectionReport && !directTemplateOrder) {
            // ä¿®æ”¹æˆå“çŠ¶æ€
            // åˆ¤æ–­æ˜¯å¦æœ‰ä¸åˆæ ¼
            Long unqualifiedCount = insReportService.getUnqualifiedCount(order);
@@ -1124,29 +1424,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);
        }
    }
@@ -1162,6 +1511,43 @@
    @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("复核参数不完整");
        }
        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()
@@ -1175,31 +1561,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();
@@ -1248,7 +1668,7 @@
            // å‘送消息的人
            // æŸ¥è¯¢å‘送人信息
            String sendUserAccount = insProductMapper.selectUserById(report.getWriteUserId()).get("account");
            String sendUserAccount = insProductMapper.selectUserById(writeUserId).get("account");
            // å‘送企业微信通知(检验任务退回)
            threadPoolTaskExecutor.execute(() -> {
@@ -1280,11 +1700,43 @@
        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;
    }
    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*/
        //这里的insSamples是订单下的所有样品包括("/")
        List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery().eq(InsSample::getInsOrderId, orderId));
@@ -1312,10 +1764,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);
        }
    }
@@ -1414,7 +1866,7 @@
            return null;
        }
        InsOrder order = insOrderMapper.selectFirstSubmit(dto.getId());
        getTemplateThing(order, Collections.unmodifiableList(insProducts));
        getTemplateThing(order, Collections.unmodifiableList(insProducts), dto.getLaboratory());
        return insProducts;
    }
@@ -1761,7 +2213,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);
@@ -1954,12 +2406,9 @@
            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);
@@ -2454,7 +2903,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();
@@ -2543,12 +2992,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());
@@ -2694,11 +3147,20 @@
        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) {
@@ -2709,7 +3171,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")));
@@ -2803,7 +3265,7 @@
            sendTime = reportInventory.getDeclareDate();
        }
        // æ£€éªŒæ—¶é—´  æŠ½æ ·æ—¶é—´-提交时间
        // æ£€æµ‹æ—¥æœŸï¼šé¦–次保存模板值时间-首次提交时间
        LocalDateTime now = LocalDateTime.now();
        // æäº¤æ—¶é—´
        LocalDateTime submitTime = LocalDateTime.now();
@@ -2812,15 +3274,20 @@
            submitTime = insOrder.getFirstSubmitDate();
        }
        String insTime = insOrder.getSendTime().format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日")) + "-"
        LocalDateTime firstInspectDate = insOrder.getFirstInspectDate();
        // å­—段上线前的历史订单沿用原来的下发时间,避免改变既有报告口径。
        if (firstInspectDate == null) {
            firstInspectDate = insOrder.getSendTime();
        }
        String insTime = firstInspectDate.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日")) + "-"
                + submitTime.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日"));
        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;
@@ -2829,7 +3296,7 @@
        List<Map<String, String>> finalDeviceList = deviceList;
        String finalModelStr = modelStr;
        List<Map<String, String>> detectionResultRows = buildDetectionResultRows(samples);
        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, "委托检测");
@@ -2850,6 +3317,29 @@
                productionDate = reportInventory.getProductDate().format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日"));
            }
            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å¹´MM月dd日"));
            }
            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"))
@@ -2883,6 +3373,8 @@
        String finalResultRemark = resultRemark;
        String equipmentText = buildEquipmentText(finalDeviceList);
        String issueDate = now.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日"));
        String finalReportModelStr = finalModelStr;
        LocalDateTime finalReportSendTime = finalSendTime;
        XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(
                new HashMap<String, Object>() {{
                    put("order", insOrder);
@@ -2895,7 +3387,7 @@
                    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å¹´MM月dd日")));
                    put("createTimeEn", monthNames[now.getMonthValue() - 1] + " " + now.format(DateTimeFormatter.ofPattern("dd, yyyy")));
@@ -2907,8 +3399,8 @@
                    put("examineUrl", null);
                    put("ratifyUrl", null);
                    put("orderType", finalOrderType);
                    put("getTime", finalSendTime.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日")));
                    put("getTimeEn", monthNames[finalSendTime.getMonthValue() - 1] + " " + finalSendTime.format(DateTimeFormatter.ofPattern("dd, yyyy")));
                    put("getTime", finalReportSendTime.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日")));
                    put("getTimeEn", monthNames[finalReportSendTime.getMonthValue() - 1] + " " + finalReportSendTime.format(DateTimeFormatter.ofPattern("dd, yyyy")));
                    put("seal1", null);
                    put("seal2", null);
                    put("formTypeCh", formType);
@@ -2920,14 +3412,14 @@
                    put("no", insReport.getCode());
                    put("cover", finalProductAndModel);
                    put("product", finalProductName);
                    put("model", defaultReportText(finalModelStr, "/"));
                    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", finalSendTime.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日")));
                    put("receive", finalReportSendTime.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日")));
                    put("sampleNo", finalSampleCode);
                    put("appearance", defaultReportText(sampleStatus, "/"));
                    put("sender", defaultReportText(insOrder.getPrepareUser(), "/"));
@@ -2949,22 +3441,34 @@
            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 static List<Map<String, String>> buildDetectionResultRows(List<SampleProductDto> samples) {
    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;
@@ -3000,6 +3504,120 @@
        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++));
            row.put("itemName", defaultReportText(itemName,
                    defaultReportText(snapshot.getName(), "模板ID " + snapshot.getTemplateId())));
            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 "/";
@@ -3018,6 +3636,54 @@
        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 StringUtils.isBlank(fallback) ? "" : fallback + unit;
        }
        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 getMapText(Map<String, String> map, String... keys) {
        if (map == null) {
            return null;
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java
@@ -17,19 +17,27 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.InventoryDetailDTO;
import com.ruoyi.basic.dto.StandardTreeTemplateBindingVo;
import com.ruoyi.basic.dto.TemplateInspectionItemDto;
import com.ruoyi.basic.mapper.IfsInventoryQuantityMapper;
import com.ruoyi.basic.mapper.StandardProductListMapper;
import com.ruoyi.basic.mapper.StructureItemParameterMapper;
import com.ruoyi.basic.pojo.IfsInventoryQuantity;
import com.ruoyi.basic.pojo.StandardProductList;
import com.ruoyi.basic.pojo.StandardMethod;
import com.ruoyi.basic.pojo.StructureItemParameter;
import com.ruoyi.basic.pojo.StructureTestObject;
import com.ruoyi.basic.pojo.StandardTree;
import com.ruoyi.basic.pojo.StandardTemplate;
import com.ruoyi.basic.service.StandardTemplateService;
import com.ruoyi.basic.service.StandardMethodService;
import com.ruoyi.basic.service.StandardTreeService;
import com.ruoyi.basic.utils.StandardTreePathParser;
import com.ruoyi.common.constant.InsOrderTypeConstants;
import com.ruoyi.common.core.domain.Result;
import com.ruoyi.common.core.domain.entity.User;
import com.ruoyi.common.enums.OrderType;
import com.ruoyi.common.numgen.NumberGenerator;
import com.ruoyi.common.utils.LimsDateUtil;
import com.ruoyi.common.utils.QueryWrappers;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.api.IfsApiUtils;
@@ -43,6 +51,8 @@
import com.ruoyi.inspect.service.InsSampleService;
import com.ruoyi.inspect.vo.IfsOrderVO;
import com.ruoyi.inspect.vo.InsOrderPrintingVo;
import com.ruoyi.inspect.vo.OrderTemplateSummaryVo;
import com.ruoyi.inspect.vo.TemplateConditionVo;
import com.ruoyi.system.mapper.UserMapper;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
@@ -90,6 +100,11 @@
    private SpotCheckQuarterItemMapper spotCheckQuarterItemMapper;
    private StandardProductListMapper standardProductListMapper;
    private StructureItemParameterMapper structureItemParameterMapper;
    private StandardTreeService standardTreeService;
    private StandardMethodService standardMethodService;
    private StandardTemplateService standardTemplateService;
    private InsOrderStandardTemplateMapper insOrderStandardTemplateMapper;
    private InsOrderTemplateDeviceMapper insOrderTemplateDeviceMapper;
@@ -407,6 +422,142 @@
    }
    /**
     * æ–°èšæ ‡å‡†åº“直绑模板的下单流程:一个订单、一个样品,检验记录完全由模板标记生成。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addNewJuInsOrder(InsPlaceOrderDto dto) {
        if (dto == null || dto.getInsOrder() == null || dto.getStandardTreeId() == null) {
            throw new ErrorException("下单信息或标准不能为空");
        }
        StandardTree standard = standardTreeService.getById(dto.getStandardTreeId());
        if (standard == null || StringUtils.isBlank(standard.getSample())
                || standard.getSampleType() != null || standard.getModel() != null) {
            throw new ErrorException("请选择标准库二级标准");
        }
        List<StandardTreeTemplateBindingVo> bindings = standardTreeService.selectTemplateBindings(dto.getStandardTreeId());
        if (bindings.isEmpty()) {
            throw new ErrorException("该标准尚未绑定可用的原始记录模板");
        }
        Map<Integer, StandardTreeTemplateBindingVo> bindingMap = bindings.stream()
                .collect(Collectors.toMap(StandardTreeTemplateBindingVo::getTemplateId,
                        binding -> binding, (first, ignored) -> first));
        List<OrderTemplateSelectionDto> selections = dto.getTemplateList();
        if (selections == null) {
            selections = bindings.stream().map(binding -> {
                OrderTemplateSelectionDto selection = new OrderTemplateSelectionDto();
                selection.setTemplateId(binding.getTemplateId());
                selection.setSort(binding.getSort());
                return selection;
            }).collect(Collectors.toList());
        }
        if (selections.isEmpty()) {
            throw new ErrorException("请至少保留一个原始记录模板");
        }
        Set<Integer> selectedTemplateIds = new HashSet<>();
        for (int index = 0; index < selections.size(); index++) {
            OrderTemplateSelectionDto selection = selections.get(index);
            if (selection == null || selection.getTemplateId() == null
                    || !bindingMap.containsKey(selection.getTemplateId())) {
                throw new ErrorException("所选原始记录模板不属于当前标准");
            }
            if (!selectedTemplateIds.add(selection.getTemplateId())) {
                throw new ErrorException("原始记录模板不能重复选择");
            }
            if (selection.getSort() == null) {
                selection.setSort(index);
            }
        }
        selections.sort(Comparator.comparing(OrderTemplateSelectionDto::getSort)
                .thenComparing(OrderTemplateSelectionDto::getTemplateId));
        Map<Integer, Integer> templateSortMap = selections.stream()
                .collect(Collectors.toMap(OrderTemplateSelectionDto::getTemplateId,
                        OrderTemplateSelectionDto::getSort));
        List<TemplateInspectionItemDto> templateItems = standardTreeService
                .selectTemplateInspectionItems(dto.getStandardTreeId()).stream()
                .filter(item -> selectedTemplateIds.contains(item.getTemplateId()))
                .sorted(Comparator
                        .comparing((TemplateInspectionItemDto item) -> templateSortMap.get(item.getTemplateId()))
                        .thenComparing(item -> item.getTemplateRowIndex() == null
                                ? Integer.MAX_VALUE : item.getTemplateRowIndex()))
                .collect(Collectors.toList());
        StandardMethod standardMethod = standardMethodService.getById(standard.getStandardMethodId());
        if (standardMethod == null || StringUtils.isBlank(standardMethod.getCode())) {
            throw new ErrorException("标准编号不存在");
        }
        String standardCode = standardMethod.getCode().trim();
        InsOrder order = dto.getInsOrder();
        order.setId(null);
        order.setState(0);
        order.setIsExemption(0);
        order.setFactory(null);
        order.setLaboratory(standard.getLaboratory());
        order.setSampleType(null);
        order.setSample(standardCode);
        order.setPrepareUserEn(null);
        order.setPrepareCode(null);
        order.setRemarkEn(null);
        // æ–°èšè®¢å•的环境条件由检验任务填写,避免下单模板或浏览器缓存带入旧值。
        order.setTemperature(null);
        order.setHumidity(null);
        insOrderMapper.insert(order);
        InsSample sample = new InsSample();
        sample.setInsOrderId(order.getId());
        sample.setSampleCode(dto.getSampleCode());
        sample.setFactory(null);
        sample.setLaboratory(standard.getLaboratory());
        sample.setSampleType(null);
        sample.setSample(standardCode);
        sample.setModel(null);
        sample.setQuantity(1);
        sample.setStandardMethodListId(null);
        insSampleMapper.insert(sample);
        int productSort = 0;
        for (TemplateInspectionItemDto item : templateItems) {
            InsProduct product = new InsProduct();
            product.setInsSampleId(sample.getId());
            product.setInspectionItem(item.getInspectionItem());
            product.setInspectionItemClass("");
            product.setInspectionItemSubclass("");
            product.setFactory(null);
            product.setLaboratory(standard.getLaboratory());
            product.setSampleType(null);
            product.setSample(standardCode);
            product.setModel(null);
            product.setTemplateId(item.getTemplateId());
            product.setTemplateRowIndex(item.getTemplateRowIndex());
            product.setSonLaboratory("新聚");
            product.setState(1);
            product.setSort(productSort++);
            insProductMapper.insert(product);
        }
        saveTemplateSnapshots(order.getId(), selections);
        return order.getId();
    }
    private void saveTemplateSnapshots(Integer orderId, List<OrderTemplateSelectionDto> selections) {
        for (OrderTemplateSelectionDto selection : selections) {
            StandardTemplate template = standardTemplateService.getById(selection.getTemplateId());
            if (template == null) {
                throw new ErrorException("原始记录模板不存在");
            }
            InsOrderStandardTemplate snapshot = new InsOrderStandardTemplate();
            snapshot.setInsOrderId(orderId);
            snapshot.setTemplateId(template.getId());
            snapshot.setName(template.getName());
            snapshot.setNumber(template.getNumber());
            snapshot.setRemark(template.getRemark());
            snapshot.setThing(template.getThing());
            snapshot.setSort(selection.getSort());
            insOrderStandardTemplateMapper.insert(snapshot);
        }
    }
    /**
     * æ·»åŠ æ£€éªŒé¡¹
     * @param sampleId
     * @param productList
@@ -447,9 +598,33 @@
            list = insSampleMapper.selectSampleProductListByOrderId2(id);
        }
        Map<String, Object> map1 = insSampleMapper.selectInsOrder(id);
        List<InsSample> orderSamples = insSampleMapper.selectList(
                Wrappers.<InsSample>lambdaQuery()
                        .eq(InsSample::getInsOrderId, id)
                        .select(InsSample::getSampleCode)
                        .orderByAsc(InsSample::getId));
        List<OrderTemplateSummaryVo> templateList = insOrderStandardTemplateMapper.selectList(
                        Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                                .eq(InsOrderStandardTemplate::getInsOrderId, id)
                                .orderByAsc(InsOrderStandardTemplate::getSort,
                                        InsOrderStandardTemplate::getId))
                .stream()
                .map(template -> {
                    OrderTemplateSummaryVo summary = new OrderTemplateSummaryVo();
                    summary.setTemplateId(template.getTemplateId());
                    summary.setName(template.getName());
                    summary.setNumber(template.getNumber());
                    summary.setRemark(template.getRemark());
                    summary.setSort(template.getSort());
                    return summary;
                })
                .collect(Collectors.toList());
        map.put("insOrder", insOrder);
        map.put("sampleProduct", list);
        // æ ·å“ç¼–号直接从样品表读取,不依赖检验项目关联查询是否有结果。
        map.put("sampleCode", orderSamples.isEmpty() ? null : orderSamples.get(0).getSampleCode());
        map.put("insOrderTemplate", map1);
        map.put("templateList", templateList);
        return map;
    }
@@ -463,24 +638,15 @@
        insOrder.setExamineTime(LocalDateTime.now());
        if (insOrder.getState() == 1) {
            //审核通过才会生成委托编号
            // todo: æ£€éªŒç±»åž‹ç¼–号
            InsOrder order = this.getById(insOrder.getId());
            String code = "";
            switch (order.getOrderType()) {
                case InsOrderTypeConstants.SPOT_CHECK:
                    code = "C";
                    break;
                case InsOrderTypeConstants.CUSTOMER_ORDERED:
                    code = "W";
                    break;
                case InsOrderTypeConstants.ENTER_THE_FACTORY:
                case InsOrderTypeConstants.QUARTERLY_TEST:
                    code = "Y";
                    break;
            }
            // ç”Ÿæˆç¼–号
            String no = numberGenerator.generateNumberWithPrefix(3,
                    "JCZX/ZB-" + code + LimsDateUtil.resetDate(LocalDateTime.now()),
            // å§”托编号按送样日期独立流水,例如: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);
            // åˆ¤æ–­æ˜¯å¦æ˜¯å­£åº¦æ£€éªŒ, æ˜¯å­£åº¦æ£€éªŒå–消原材料季度检验下单
            if (InsOrderTypeConstants.QUARTERLY_TEST.equals(order.getOrderType())) {
@@ -525,6 +691,39 @@
        Map<String, Object> map = new HashMap<>();
//        InsOrder insOrder = insOrderMapper.selectById(id);
        IfsOrderVO ifsOrderVO = insOrderMapper.selectOrderInfoById(id);
        Map<Integer, List<Integer>> templateDeviceIds = insOrderTemplateDeviceMapper.selectList(
                        Wrappers.<InsOrderTemplateDevice>lambdaQuery()
                                .eq(InsOrderTemplateDevice::getInsOrderId, id)
                                .orderByAsc(InsOrderTemplateDevice::getSort,
                                        InsOrderTemplateDevice::getId))
                .stream().collect(Collectors.groupingBy(
                        InsOrderTemplateDevice::getTemplateId,
                        LinkedHashMap::new,
                        Collectors.mapping(InsOrderTemplateDevice::getDeviceId, Collectors.toList())));
        List<TemplateConditionVo> templateConditions = insOrderStandardTemplateMapper.selectList(
                        Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                                .eq(InsOrderStandardTemplate::getInsOrderId, id)
                                .select(InsOrderStandardTemplate::getTemplateId,
                                        InsOrderStandardTemplate::getTemperature,
                                        InsOrderStandardTemplate::getHumidity,
                                        InsOrderStandardTemplate::getDetectionTime,
                                        InsOrderStandardTemplate::getDetectionPlace,
                                        InsOrderStandardTemplate::getInsResult)
                                .orderByAsc(InsOrderStandardTemplate::getSort,
                                        InsOrderStandardTemplate::getId))
                .stream().map(snapshot -> {
                    TemplateConditionVo condition = new TemplateConditionVo();
                    condition.setTemplateId(snapshot.getTemplateId());
                    condition.setTemperature(snapshot.getTemperature());
                    condition.setHumidity(snapshot.getHumidity());
                    condition.setDetectionTime(snapshot.getDetectionTime());
                    condition.setDetectionPlace(snapshot.getDetectionPlace());
                    condition.setInsResult(snapshot.getInsResult());
                    condition.setDeviceIds(templateDeviceIds.getOrDefault(
                            snapshot.getTemplateId(), Collections.emptyList()));
                    return condition;
                }).collect(Collectors.toList());
        ifsOrderVO.setTemplateConditions(templateConditions);
        List<SampleProductDto> list = insSampleMapper.getInsOrderAndSample(id, laboratory);
        map.put("insOrder", ifsOrderVO);
        map.put("sampleProduct", list);
@@ -969,11 +1168,18 @@
        InsProduct insProduct = insProductMapper.selectOne(Wrappers.<InsProduct>lambdaQuery()
                .eq(InsProduct::getInsSampleId, insSampleId)
                .last("limit 1"));
        String tree = insProduct.getFactory() + " - " +
                insProduct.getLaboratory() + " - " +
                insProduct.getSampleType() + " - " +
                insProduct.getSample() + " - " +
                insProduct.getModel();
        String tree;
        if (insProduct.getSampleType() == null && insProduct.getModel() == null
                && StringUtils.isNotBlank(insProduct.getLaboratory())
                && StringUtils.isNotBlank(insProduct.getSample())) {
            tree = StandardTreePathParser.canonical(insProduct.getLaboratory(), insProduct.getSample());
        } else {
            tree = insProduct.getFactory() + " - " +
                    insProduct.getLaboratory() + " - " +
                    insProduct.getSampleType() + " - " +
                    insProduct.getSample() + " - " +
                    insProduct.getModel();
        }
        // æŸ¥è¯¢æ ‡å‡†æ ‘
        List<StandardProductList> standardProductLists = standardProductListMapper.selectList(Wrappers.<StandardProductList>lambdaQuery()
                .eq(StandardProductList::getStandardMethodListId, insProduct.getStandardMethodListId())
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsProductServiceImpl.java
@@ -7,12 +7,18 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.StandardProductListMapper;
import com.ruoyi.basic.pojo.StandardProductList;
import com.ruoyi.basic.utils.StandardTreePathParser;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.inspect.dto.InsProductBindingDto;
import com.ruoyi.inspect.dto.WriteInspectionConditionDto;
import com.ruoyi.inspect.mapper.InsOrderMapper;
import com.ruoyi.inspect.mapper.InsOrderStandardTemplateMapper;
import com.ruoyi.inspect.mapper.InsOrderTemplateDeviceMapper;
import com.ruoyi.inspect.mapper.InsProductMapper;
import com.ruoyi.inspect.mapper.InsSampleMapper;
import com.ruoyi.inspect.pojo.InsOrder;
import com.ruoyi.inspect.pojo.InsOrderStandardTemplate;
import com.ruoyi.inspect.pojo.InsOrderTemplateDevice;
import com.ruoyi.inspect.pojo.InsProduct;
import com.ruoyi.inspect.pojo.InsSample;
import com.ruoyi.inspect.service.InsProductService;
@@ -39,6 +45,10 @@
    private StandardProductListMapper standardProductListMapper;
    private InsOrderStandardTemplateMapper insOrderStandardTemplateMapper;
    private InsOrderTemplateDeviceMapper insOrderTemplateDeviceMapper;
    @Override
    public int selectOrderManDay(Integer orderId) {
        return insProductMapper.selectOrderManDay(orderId);
@@ -54,7 +64,69 @@
    }
    @Override
    public boolean write(InsOrder insOrder) {
    public boolean write(WriteInspectionConditionDto dto) {
        if (dto == null || dto.getId() == null) {
            throw new ErrorException("订单ID不能为空");
        }
        if (dto.getTemplateId() != null) {
            InsOrderStandardTemplate snapshot = insOrderStandardTemplateMapper.selectOne(
                    Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                            .eq(InsOrderStandardTemplate::getInsOrderId, dto.getId())
                            .eq(InsOrderStandardTemplate::getTemplateId, dto.getTemplateId())
                            .last("limit 1"));
            if (snapshot == null) {
                throw new ErrorException("未找到当前模板的原始记录快照");
            }
            boolean hasSnapshotField = dto.isTemperaturePresent() || dto.isHumidityPresent()
                    || dto.isDetectionTimePresent() || dto.isDetectionPlacePresent()
                    || dto.isInsResultPresent();
            if (hasSnapshotField) {
                insOrderStandardTemplateMapper.update(null,
                        Wrappers.<InsOrderStandardTemplate>lambdaUpdate()
                                .eq(InsOrderStandardTemplate::getId, snapshot.getId())
                                .set(dto.isTemperaturePresent(),
                                        InsOrderStandardTemplate::getTemperature, dto.getTemperature())
                                .set(dto.isHumidityPresent(),
                                        InsOrderStandardTemplate::getHumidity, dto.getHumidity())
                                .set(dto.isDetectionTimePresent(),
                                        InsOrderStandardTemplate::getDetectionTime, dto.getDetectionTime())
                                .set(dto.isDetectionPlacePresent(),
                                        InsOrderStandardTemplate::getDetectionPlace, dto.getDetectionPlace())
                                .set(dto.isInsResultPresent(),
                                        InsOrderStandardTemplate::getInsResult, dto.getInsResult()));
            }
            if (dto.isDeviceIdsPresent()) {
                insOrderTemplateDeviceMapper.delete(
                        Wrappers.<InsOrderTemplateDevice>lambdaQuery()
                                .eq(InsOrderTemplateDevice::getInsOrderId, dto.getId())
                                .eq(InsOrderTemplateDevice::getTemplateId, dto.getTemplateId()));
                List<Integer> deviceIds = dto.getDeviceIds() == null
                        ? Collections.emptyList()
                        : dto.getDeviceIds().stream()
                                .filter(Objects::nonNull)
                                .distinct()
                                .collect(Collectors.toList());
                if (!deviceIds.isEmpty()
                        && insOrderTemplateDeviceMapper.selectExistingDeviceIds(deviceIds).size() != deviceIds.size()) {
                    throw new ErrorException("所选设备不存在,请刷新设备列表后重试");
                }
                for (int index = 0; index < deviceIds.size(); index++) {
                    InsOrderTemplateDevice relation = new InsOrderTemplateDevice();
                    relation.setInsOrderId(dto.getId());
                    relation.setTemplateId(dto.getTemplateId());
                    relation.setDeviceId(deviceIds.get(index));
                    relation.setSort(index);
                    insOrderTemplateDeviceMapper.insert(relation);
                }
            }
            if (!hasSnapshotField && !dto.isDeviceIdsPresent()) {
                throw new ErrorException("没有需要保存的模板检验条件");
            }
            return true;
        }
        InsOrder insOrder = new InsOrder();
        insOrder.setId(dto.getId());
        insOrder.setRemark(dto.getRemark());
        insOrderMapper.updateById(insOrder);
        return true;
    }
@@ -118,11 +190,18 @@
    public List<InsProduct> getProductTreeByProductId(Integer productId) {
        // æŸ¥è¯¢æ£€éªŒé¡¹ä¿¡æ¯
        InsProduct insProduct = insProductMapper.selectById(productId);
        String tree = insProduct.getFactory() + " - " +
                insProduct.getLaboratory() + " - " +
                insProduct.getSampleType() + " - " +
                insProduct.getSample() + " - " +
                insProduct.getModel();
        String tree;
        if (insProduct.getSampleType() == null && insProduct.getModel() == null
                && StringUtils.isNotBlank(insProduct.getLaboratory())
                && StringUtils.isNotBlank(insProduct.getSample())) {
            tree = StandardTreePathParser.canonical(insProduct.getLaboratory(), insProduct.getSample());
        } else {
            tree = insProduct.getFactory() + " - " +
                    insProduct.getLaboratory() + " - " +
                    insProduct.getSampleType() + " - " +
                    insProduct.getSample() + " - " +
                    insProduct.getModel();
        }
        // æŸ¥è¯¢æ ‡å‡†æ ‘
        List<StandardProductList> standardProductLists = standardProductListMapper.selectList(Wrappers.<StandardProductList>lambdaQuery()
                .eq(StandardProductList::getStandardMethodListId, insProduct.getStandardMethodListId())
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsReportServiceImpl.java
@@ -576,6 +576,47 @@
        return "/word/zip/output.zip";
    }
    @Override
    public void download(Integer id, Integer createOrderUser, HttpServletResponse response) {
        if (id == null) {
            throw new ErrorException("报告不能为空");
        }
        InsReport report = insReportMapper.selectDownloadableReport(id, createOrderUser);
        if (report == null) {
            throw new ErrorException("报告不存在、尚未生成或无权下载");
        }
        String reportUrl = StringUtils.isNotBlank(report.getUrlS()) ? report.getUrlS() : report.getUrl();
        if (StringUtils.isBlank(reportUrl) || !reportUrl.replace('\\', '/').startsWith("/word/")) {
            throw new ErrorException("报告文件不存在,请联系管理员");
        }
        Path root = Paths.get(wordUrl).toAbsolutePath().normalize();
        Path file = root.resolve(reportUrl.replace('\\', '/').substring("/word/".length())).normalize();
        if (!file.startsWith(root) || !Files.isRegularFile(file)) {
            throw new ErrorException("报告文件不存在,请联系管理员");
        }
        try {
            String filename = file.getFileName().toString();
            String encodedName = URLEncoder.encode(filename, "UTF-8").replace("+", "%20");
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedName);
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
            response.setHeader("download-filename", encodedName);
            response.setContentLengthLong(Files.size(file));
            try (InputStream inputStream = Files.newInputStream(file);
                 OutputStream outputStream = response.getOutputStream()) {
                byte[] buffer = new byte[8192];
                int length;
                while ((length = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, length);
                }
                outputStream.flush();
            }
        } catch (IOException e) {
            log.error("下载报告失败,reportId={}", id, e);
            throw new ErrorException("报告下载失败");
        }
    }
    //批量上传
    @Override
    @Transactional(rollbackFor = Exception.class)
inspect-server/src/main/java/com/ruoyi/inspect/vo/IfsOrderVO.java
@@ -6,6 +6,7 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
 * ifs订单vo
@@ -73,4 +74,7 @@
    private String lotBatchNo;
    @ApiModelProperty("各原始记录模板的环境条件")
    private List<TemplateConditionVo> templateConditions;
}
inspect-server/src/main/java/com/ruoyi/inspect/vo/OrderTemplateSummaryVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.inspect.vo;
import lombok.Data;
/** è®¢å•原始记录模板的轻量展示信息。 */
@Data
public class OrderTemplateSummaryVo {
    private Integer templateId;
    private String name;
    private String number;
    private String remark;
    private Integer sort;
}
inspect-server/src/main/java/com/ruoyi/inspect/vo/TemplateConditionVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.ruoyi.inspect.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
/** åŽŸå§‹è®°å½•æ¨¡æ¿å¯¹åº”çš„æ£€éªŒçŽ¯å¢ƒæ¡ä»¶ã€‚ */
@Data
public class TemplateConditionVo {
    @ApiModelProperty("模板ID")
    private Integer templateId;
    @ApiModelProperty("温度")
    private String temperature;
    @ApiModelProperty("湿度")
    private String humidity;
    @ApiModelProperty("检测日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate detectionTime;
    @ApiModelProperty("检测地点")
    private String detectionPlace;
    @ApiModelProperty("设备ID列表")
    private List<Integer> deviceIds = new ArrayList<>();
    @ApiModelProperty("模板检验结论:0不合格,1合格")
    private Integer insResult;
}
inspect-server/src/main/resources/mapper/InsOrderMapper.xml
@@ -181,6 +181,8 @@
        type,
        entrust_code,
        company,
        sample,
        sample_view,
        remark,
        create_time,
        state
inspect-server/src/main/resources/mapper/InsReportMapper.xml
@@ -66,6 +66,19 @@
        limit 1
    </select>
    <select id="selectDownloadableReport" resultType="com.ruoyi.inspect.pojo.InsReport">
        select ir.*
        from ins_report ir
        where ir.id = #{id}
          and ir.is_pass = 1
        <if test="createOrderUser != null">
          and (ir.write_user_id = #{createOrderUser}
               or ir.examine_user_id = #{createOrderUser}
               or ir.ratify_user_id = #{createOrderUser})
        </if>
        limit 1
    </select>
    <!-- æŠ¥å‘ŠæŠ¥è¡¨å¯¼å‡º -->
    <select id="reportAllExport" resultType="com.ruoyi.inspect.dto.InsReportExport">
        select code,
inspect-server/src/main/resources/mapper/InsSampleMapper.xml
@@ -70,6 +70,7 @@
        <result property="createTime" column="ip_create_time" jdbcType="TIMESTAMP"/>
        <result property="updateTime" column="ip_update_time" jdbcType="TIMESTAMP"/>
        <result property="templateId" column="template_id" jdbcType="INTEGER"/>
        <result property="templateRowIndex" column="template_row_index" jdbcType="INTEGER"/>
        <result property="methodS" column="method_s"/>
        <result property="tell" column="tell"/>
        <result property="dic" column="dic"/>
@@ -152,6 +153,7 @@
               ip.create_time ip_create_time,
               ip.update_time ip_update_time,
               template_id,
               template_row_index,
               ipr.ins_value,
               ipr.com_value,
               ipr.equip_value,
@@ -529,7 +531,7 @@
                 left join ins_product ip on isa.id = ip.ins_sample_id
                 left join ins_product_result ipr on ip.id = ipr.ins_product_id
        where ins_order_id = #{id}
          and ip.standard_method_list_id is not null
          and (ip.standard_method_list_id is not null or ip.template_row_index is not null)
          and state = 1
          and ip.is_binding != 1
        order by ip.sort asc,
@@ -550,7 +552,7 @@
          and ip.son_laboratory = #{laboratory}
          and ins_fiber_id is null
          and ins_fibers_id is null
          and standard_method_list_id is not null
          and (standard_method_list_id is not null or template_row_index is not null)
        /* ç”µç¼†é…ç½®æ ‡è¯† */
        <if test="cableTag == null or cableTag == ''">
            and ip.cable_tag is null
@@ -577,7 +579,7 @@
        and ip.son_laboratory = #{laboratory}
        and ins_fiber_id is null
        and ins_fibers_id is null
        and standard_method_list_id is not null
        and (standard_method_list_id is not null or template_row_index is not null)
        and ip.raw_material_tag = #{rawMaterialTag}
        order by sort asc
    </select>