package com.cf.imes.module.plan.service.orderImport.factory;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.exception.ErrorCode;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.id.core.util.SnowflakeIdWorker3rd;
import com.cf.imes.framework.mybatis.core.generator.SnowFlakeGenerator;
import com.cf.imes.framework.mybatis.core.injector.BaseBatchMapper;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.executor.enums.OrderItemTypeEnum;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataBlockRemarkVO;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataBlockReqVO;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataComponentGroupReqVO;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataDoubleRoomTreeReqVO;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataGroupInfoReqVO;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataMaterialReqVO;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataPartsReqVO;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataProcessGroupReqVO;
import com.cf.imes.module.plan.controller.admin.orderimport.webcad.vo.WebCadDataReqVO;
import com.cf.imes.module.plan.dal.dataobject.goods.GoodsDO;
import com.cf.imes.module.plan.dal.dataobject.ordecomponent.OrderComponentDO;
import com.cf.imes.module.plan.dal.dataobject.order.OrderDO;
import com.cf.imes.module.plan.dal.dataobject.orderBody.OrderBodyDO;
import com.cf.imes.module.plan.dal.dataobject.orderGroup.OrderGroupDO;
import com.cf.imes.module.plan.dal.dataobject.orderItem.OrderItemDO;
import com.cf.imes.module.plan.dal.dataobject.orderParts.OrderPartsDO;
import com.cf.imes.module.plan.dal.dataobject.ordermodel.OrderModelDO;
import com.cf.imes.module.plan.dal.dataobject.plate.PlateDO;
import com.cf.imes.module.plan.dal.elasticsearch.HoleDetail;
import com.cf.imes.module.plan.dal.elasticsearch.IContourData;
import com.cf.imes.module.plan.dal.elasticsearch.IOriginModelingData;
import com.cf.imes.module.plan.dal.elasticsearch.ModelDetail;
import com.cf.imes.module.plan.dal.elasticsearch.OffSetList;
import com.cf.imes.module.plan.dal.elasticsearch.Point;
import com.cf.imes.module.plan.dal.elasticsearch.PointDetail;
import com.cf.imes.module.plan.dal.elasticsearch.PointList;
import com.cf.imes.module.plan.dal.mysql.goods.GoodsMapper;
import com.cf.imes.module.plan.dal.mysql.order.OrderMapper;
import com.cf.imes.module.plan.dal.mysql.orderBody.OrderBodyMapper;
import com.cf.imes.module.plan.dal.mysql.plate.PlateMapper;
import com.cf.imes.module.plan.enums.ErrorCodeConstants;
import com.cf.imes.module.plan.enums.ordercomponent.OrderComponentProcessStatusEnum;
import com.cf.imes.module.plan.service.customplateno.CustomPlateNoGenerateService;
import com.cf.imes.module.plan.service.customplateno.vo.CustomPlateNoGenerateConfigVO;
import com.cf.imes.module.plan.service.orderImport.vo.OrderComponentTreeCompareVO;
import com.cf.imes.module.system.api.assemblyconfig.AssemblyConfigApi;
import com.cf.imes.module.system.api.assemblyconfig.dto.AssemblyConfigRespDTO;
import com.cf.imes.module.system.api.systemconfig.SystemConfigApi;
import com.cf.imes.module.system.api.systemconfig.dto.SystemConfigRespDTO;
import com.cf.imes.module.system.api.systemconfig.dto.SystemConfigSealEdgeRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.cf.imes.module.executor.enums.ErrorCodeConstants.ORDER_COMPONENT_ASSEMBLY_CONFIG_QUERY_ERROR;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_GET_ORG_SEALEDGE_ERROR;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_CHECK_ERROR;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_REACH_THRESHOLD_ERROR;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORG_SEALEDGE_ANALYZE_ERROR;

/**
 * webcad导入订单工厂服务
 *
 * @author Gqr
 * @since 2025/3/8 14:39
 */
@Slf4j
public class WebCadOrderImportFactory {
    /**
     * 批量操作阈值
     */
    private static final int BATCH_THRESHOLD_NUMBER = 999;

    private SnowFlakeGenerator snowFlakeGenerator;

    private OrderMapper orderMapper;

    private OrderBodyMapper orderBodyMapper;

    private PlateMapper plateMapper;

    private SnowflakeIdWorker3rd idWorker;

    private SystemConfigApi systemConfigApi;

    private AssemblyConfigApi assemblyConfigApi;

    private GoodsMapper goodsMapper;

    private CustomPlateNoGenerateService customPlateNoGenerateService;

    private JdbcTemplate jdbcTemplate;

    private Long organId;

    // 批次id
    private Long batchId;

    private Long orderId;

    private OrderDO orderDO;

    // 是否入库的第一块板件
    private boolean isFirstPlate;

    private CustomPlateNoGenerateConfigVO plateNoGenerateConfigVO;

    // 操作人
    private String operatorName;

    private LocalDateTime now;

    // 柜体成倍信息缓存：{ 房间名+柜体名：count }
    private Map<String, Integer> roomBodyMap = new HashMap<>();

    // 柜体缓存
    private Map<String, OrderBodyDO> bodyMap = new HashMap<>();

    // 房间名+柜体名+加工组名：加工组
    private Map<String, OrderGroupDO> orderGroupMap = new HashMap<>();

    // 板材缓存：{商品编号：GoodsDO}
    private Map<String, GoodsDO> materialMap = new HashMap<>();

    // 已存在房间缓存：{ roomName: roomId }
    private Map<String, Long> existRoomIdMap = new HashMap<>();

    // IsComposite+ObjectType+Factory+Model+Spec+height+width+depth+unit+brand：orderPartId
    private Map<String, Long> orderPartMap = new HashMap<>();

    // 加工组缓存
    private Map<Integer, WebCadDataGroupInfoReqVO> processGroupMap = new HashMap<>();

    // 部件缓存：{cad groupId：imes部件id}
    private Map<Integer, OrderComponentDO> componentGroupMap = new HashMap<>();

    private List<PlateDO> plateDOS = new ArrayList<>();
    private List<OrderItemDO> orderItemDOS = new ArrayList<>();
    private List<OrderPartsDO> orderPartsDOS = new ArrayList<>();
    private List<GoodsDO> goodsDOS = new ArrayList<>();
    private List<OrderComponentDO> orderComponentDOS = new ArrayList<>();
    private List<SystemConfigSealEdgeRespDTO> sealEdgeConfigList;

    private static final String PART_CATEGORY_ONE = "封边条";
    private static final String PART_CATEGORY_TWO = "五金";
    private static final String PART_CATEGORY_THREE = "组件";
    private static final String DELETED_FIELD = "deleted";
    private static final String ORGAN_ID_FIELD = "organ_id";
    private static final String ORDER_ID_FIELD = "order_id";
    private static final String ROOM_NAME_FIELD = "room_name";
    private static final String EMPTY_STRING = new String();

    // 当前拆单导入的板件数量（初始包含前n次导入的板件总数）
    private int currentPlateNum;

    private BigDecimal currentPlateArea = new BigDecimal(0);

    // cad拆单板件数量阈值
    private int cadImportPlateNumThreshold;

    // 板件数量是否已达到阈值
    private boolean plateNumReachThreshold = false;

    // cad图纸文件id
    private Long cadViewFileId;

    public WebCadOrderImportFactory(Long organId,
                                    OrderDO orderDO,
                                    SnowFlakeGenerator snowFlakeGenerator,
                                    OrderMapper orderMapper,
                                    OrderBodyMapper orderBodyMapper,
                                    PlateMapper plateMapper,
                                    SnowflakeIdWorker3rd idWorker,
                                    GoodsMapper goodsMapper,
                                    CustomPlateNoGenerateService customPlateNoGenerateService,
                                    SystemConfigApi systemConfigApi,
                                    AssemblyConfigApi assemblyConfigApi,
                                    int cadImportPlateNumThreshold,
                                    JdbcTemplate jdbcTemplate) {
        this.organId = organId;
        this.orderDO = orderDO;
        this.orderId = orderDO.getId();
        this.snowFlakeGenerator = snowFlakeGenerator;
        this.orderMapper = orderMapper;
        this.orderBodyMapper = orderBodyMapper;
        this.plateMapper = plateMapper;
        this.idWorker = idWorker;
        this.goodsMapper = goodsMapper;
        this.customPlateNoGenerateService = customPlateNoGenerateService;
        this.systemConfigApi = systemConfigApi;
        this.assemblyConfigApi = assemblyConfigApi;
        operatorName = SecurityFrameworkUtils.getLoginUser().getNickname();
        now = LocalDateTime.now();
        sealEdgeConfigList = new ArrayList<>();
        this.cadImportPlateNumThreshold = cadImportPlateNumThreshold;
        this.jdbcTemplate = jdbcTemplate;
    }

    public void analyzeTempData(WebCadDataReqVO webCadDataReqVO) {
        try {
            cadViewFileId = webCadDataReqVO.getCadViewFileId();

            log.debug("====================【cad拆单校验板件数量开始】====================");
            // 校验板件数量
            analyzePlateNum();
            log.debug("====================【cad拆单校验板件数量结束】====================");

            log.debug("====================【cad拆单解析板材开始】====================");
            // 板材id对应板件
            analyzeMaterials(webCadDataReqVO.getMaterials());
            log.debug("====================【cad拆单解析板材结束】====================");

            log.debug("====================【cad拆单解析加工组开始】====================");
            // 解析加工组类型
            analyzeGroups(webCadDataReqVO.getProcessGroup());
            log.debug("====================【cad拆单解析加工组结束】====================");

            log.debug("====================【cad拆单解析成倍柜体开始】====================");
            // 缓存柜体成倍信息
            analyzeRoomBody(webCadDataReqVO.getDouleRoomTree());
            log.debug("====================【cad拆单解析成倍柜体结束】====================");

            // 缓存部件信息
            log.debug("====================【cad拆单解析部件开始】====================");
            analyzeComponentGroup(webCadDataReqVO.getComponentGroup());
            log.debug("====================【cad拆单解析部件结束】====================");

            log.debug("====================【cad拆单解析板件开始】====================");
            // 解析板件列表
            analyzeBlockList(webCadDataReqVO.getBlocks());
            log.debug("====================【cad拆单解析板件结束】====================");

            log.debug("====================【cad拆单解析配件开始】====================");
            // 解析配件列表
            analyzeParts(webCadDataReqVO.getParts());
            log.debug("====================【cad拆单解析配件结束】====================");
            // 部件入库
            orderComponentBatchInsertWithinThreshold(orderComponentDOS, true);

            orderItemBatchInsertWithinThreshold(orderItemDOS, true);
            log.debug("====================【cad拆单全局保存开始】====================");

            // 保存数据柜体、加工组
            saveData();
            log.debug("====================【cad拆单全局保存结束】====================");

            // 新增/更新order_goods
            insertOrUpdatePlateNumGoods();

            // 更新订单板件数量
            updateOrderPlateNum();

            // 所有自定义板编号生成完成后更新配置到订单
            customPlateNoGenerateService.updateCustomPlateNoGenerateConfig(plateNoGenerateConfigVO, orderDO);

            if (plateNumReachThreshold) {
                log.warn(new ServiceException(WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_REACH_THRESHOLD_ERROR, orderId, cadImportPlateNumThreshold).getMessage());
            }

        } catch (Exception e) {
            throw e;
        } finally {
            // 清理数据
            clearAll();
        }
    }

    /**
     * 获取组织封边条对应配置
     */
    private void getOrgSealEdgeConfig() {
        CommonResult<List<SystemConfigRespDTO>> orgSealEdgeConfig = systemConfigApi.getOrgSealEdgeConfig();
        boolean success = orgSealEdgeConfig.isSuccess();
        if (success) {
            try {
                List<SystemConfigRespDTO> orgSealEdgeConfigCheckedData = orgSealEdgeConfig.getCheckedData();
                if (CollUtil.isNotEmpty(orgSealEdgeConfigCheckedData)) {
                    // 默认取第一条为配置
                    SystemConfigRespDTO sealEdgeConfig = orgSealEdgeConfigCheckedData.get(0);
                    String setting = sealEdgeConfig.getSetting();
                    // 封边对应配置转列表
                    JSONArray jsonArray = JSON.parseArray(setting);
                    for (int i = 0; i < jsonArray.size(); i++) {
                        JSONObject jsonObject = jsonArray.getJSONObject(i);
                        String widthMin = jsonObject.getString("widthMin");
                        String widthMax = jsonObject.getString("widthMax");
                        String widthValue = jsonObject.getString("widthValue");

                        if (StringUtils.isNotEmpty(widthMin) && StringUtils.isNotEmpty(widthMax) && StringUtils.isNotEmpty(widthValue)) {
                            sealEdgeConfigList.add(new SystemConfigSealEdgeRespDTO(NumberUtil.parseDouble(widthMin), NumberUtil.parseDouble(widthMax), NumberUtil.parseDouble(widthValue)));
                        }
                    }
                }
            } catch (Exception e) {
                ErrorCode webcadOrderImportOrgSealedgeAnalyzeError = WEBCAD_ORDER_IMPORT_ORG_SEALEDGE_ANALYZE_ERROR;
                log.error(webcadOrderImportOrgSealedgeAnalyzeError.getMsg(), e);
                throw new ServiceException(webcadOrderImportOrgSealedgeAnalyzeError);
            }
        } else {
            throw new ServiceException(WEBCAD_ORDER_IMPORT_GET_ORG_SEALEDGE_ERROR);
        }
    }

    /**
     * 解析订单信息
     *
     * @param
     */
    private void analyzePlateNum() {
        // 解析已有订单下板件数量，超过两万不再导入
        Long existPlateCount = plateMapper.selectCount(new LambdaQueryWrapper<PlateDO>()
                .eq(PlateDO::getOrderId, orderId)
                .eq(PlateDO::getOrganId, organId)
                .eq(PlateDO::getDeleted, false)
        );
        // cadImportPlateNumThreshold万片阻止继续拆单
        if (existPlateCount >= cadImportPlateNumThreshold) {
            throw new ServiceException(WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_CHECK_ERROR, orderId, cadImportPlateNumThreshold);
        } else {
            currentPlateNum = existPlateCount.intValue();
            // 统计当前板件总面积
            List<Map<String, Object>> orderPlateSumResult = plateMapper.selectMaps(new QueryWrapper<PlateDO>()
                    .select("sum(area) as area")
                    .eq(ORGAN_ID_FIELD, organId)
                    .eq(ORDER_ID_FIELD, orderId)
                    .eq(DELETED_FIELD, false));
            if (CollUtil.isNotEmpty(orderPlateSumResult)) {
                Map<String, Object> resultMap = orderPlateSumResult.get(0);
                if (ObjectUtil.isNotNull(resultMap)) {
                    currentPlateArea = (BigDecimal) resultMap.get("area");
                }
            }
        }
    }

    /**
     * 板材id赋值对应板材
     *
     * @param materialReqVOS
     */
    private void analyzeMaterials(List<WebCadDataMaterialReqVO> materialReqVOS) {
        if (CollUtil.isEmpty(materialReqVOS)) {
            return;
        }

        // 检查订单下是否已经有goodsId下的order_goods，有不再创建
        materialMap = goodsMapper.selectMaps(
                        new QueryWrapper<GoodsDO>()
                                .select("goods_id ,MIN(id) as id, plate_num, planned_plate_num, area, planned_plate_area, version")
                                .eq(ORDER_ID_FIELD, orderId)
                                .eq(ORGAN_ID_FIELD, organId)
                                .eq(DELETED_FIELD, false)
                                .in("goods_id", materialReqVOS.stream().map(WebCadDataMaterialReqVO::getGoodsId).collect(Collectors.toList()))
                                .groupBy("goods_id, plate_num, planned_plate_num, area, planned_plate_area, version")).stream()
                .collect(Collectors.toMap(
                        m -> String.valueOf(m.get("goods_id")),
                        m -> {
                            GoodsDO goodsDO = new GoodsDO();
                            goodsDO.setId(NumberUtil.parseLong(String.valueOf(m.get("id"))));
                            goodsDO.setExist(true);
                            goodsDO.setPlateNum(NumberUtil.parseInt(String.valueOf(m.get("plate_num"))));
                            goodsDO.setPlannedPlateNum(NumberUtil.parseInt(String.valueOf(m.get("planned_plate_num"))));
                            goodsDO.setArea(NumberUtil.parseDouble(String.valueOf(m.get("area"))));
                            goodsDO.setPlannedPlateArea(NumberUtil.parseDouble(String.valueOf(m.get("planned_plate_area"))));
                            goodsDO.setVersion(NumberUtil.parseInt(String.valueOf(m.get("version"))));
                            return goodsDO;
                        }
                ));

        for (WebCadDataMaterialReqVO materialReqVO : materialReqVOS) {
            String plateGoodsGoodsId = materialReqVO.getGoodsId();

            GoodsDO existOrderGoods = materialMap.get(plateGoodsGoodsId);
            if (ObjectUtil.isNull(existOrderGoods)) {
                // 创建设计端商品
                Long rawGoodsId = (Long) snowFlakeGenerator.nextId(null);
                Long orderGoodsId = (Long) snowFlakeGenerator.nextId(null);

                // 创建订单商品
                GoodsDO goodsDO = GoodsDO.builder()
                        .id(orderGoodsId)
                        .orderId(orderId)
                        .rawGoodsId(String.valueOf(rawGoodsId))
                        .goodsId(plateGoodsGoodsId)
                        .goodsName(materialReqVO.getName())
                        .material(materialReqVO.getMaterial())
                        .texture(materialReqVO.getTexture())
                        .color(materialReqVO.getColor())
                        .colorBlack(EMPTY_STRING)
                        .width(getBigDecimalOrNull(getZeroDouble(materialReqVO.getWidth())))
                        .height(getBigDecimalOrNull(getZeroDouble(materialReqVO.getHeight())))
                        .thickness(getBigDecimalOrNull(materialReqVO.getThickness()))
                        .spec(getEmpty(materialReqVO.getSpec()))
                        .brand(getEmpty(materialReqVO.getBrand()))
                        .remark(EMPTY_STRING)
                        .planId(0L)
                        .price(0.00)
                        .deleted(false)
                        .organId(organId)
                        .exist(false)
                        .plateNum(0)
                        .plannedPlateNum(0)
                        .area(0.00)
                        .plannedPlateArea(0.00)
                        .version(0)
                        .build();

                goodsDOS.add(goodsDO);

                // 缓存goodCode和orderGoodsId的对应关系
                materialMap.put(plateGoodsGoodsId, goodsDO);
            }
        }
    }

    /**
     * 缓存加工组类型
     *
     * @param processGroupList
     */
    private void analyzeGroups(List<WebCadDataProcessGroupReqVO> processGroupList) {
        if (CollUtil.isNotEmpty(processGroupList)) {
            for (WebCadDataProcessGroupReqVO processGroup : processGroupList) {
                String groupTypeName = processGroup.getType();
                Long groupTypeId = (Long) snowFlakeGenerator.nextId(null);
                List<WebCadDataProcessGroupReqVO.ProjectsDTO> projects = processGroup.getProjects();
                if (CollUtil.isNotEmpty(projects)) {
                    for (WebCadDataProcessGroupReqVO.ProjectsDTO project : projects) {
                        // 缓存加工组信息
                        WebCadDataGroupInfoReqVO processGroupInfo = new WebCadDataGroupInfoReqVO();
                        processGroupInfo.setGroupName(project.getProjName());
                        processGroupInfo.setGroupTypeName(groupTypeName);
                        processGroupInfo.setGroupTypeId(groupTypeId);
                        processGroupInfo.setHeight(project.getBoxSize().getHeight());
                        processGroupInfo.setWidth(project.getBoxSize().getWidth());
                        processGroupInfo.setDepth(project.getBoxSize().getDepth());
                        processGroupMap.put(project.getGroupID(), processGroupInfo);
                    }
                }
            }
        }
    }

    /**
     * 生成并缓存成倍房间柜体信息
     *
     * @param douleRoomTree
     */
    private void analyzeRoomBody(List<WebCadDataDoubleRoomTreeReqVO> douleRoomTree) {
        if (CollUtil.isEmpty(douleRoomTree)) {
            return;
        }
        for (WebCadDataDoubleRoomTreeReqVO room : douleRoomTree) {
            String roomName = room.getName();
            List<WebCadDataDoubleRoomTreeReqVO.ChildrenDTO> bodys = room.getChildren();
            if (CollUtil.isNotEmpty(bodys)) {
                for (WebCadDataDoubleRoomTreeReqVO.ChildrenDTO body : bodys) {
                    String bodyName = body.getName();
                    // 缓存柜体成倍信息
                    roomBodyMap.put(roomName + bodyName, body.getCount());
                }
            }
        }
    }

    /**
     * 解析部件信息
     *
     * @param componentGroupReqVOS
     */
    private void analyzeComponentGroup(List<WebCadDataComponentGroupReqVO> componentGroupReqVOS) {
        if (CollUtil.isEmpty(componentGroupReqVOS)) {
            return;
        }
        // 1、构建树 + 生成结构hash
        List<OrderComponentTreeCompareVO> roots =
                buildTree(componentGroupReqVOS);

        // 2、查询装配配置
        List<String> names = roots.stream()
                .map(OrderComponentTreeCompareVO::getName)
                .distinct()
                .toList();


        CommonResult<List<AssemblyConfigRespDTO>> configResult =
                assemblyConfigApi.getNotProcessAssemblyConfigByPartNameList(names);

        if (configResult.isError()) {
            throw new ServiceException(
                    ORDER_COMPONENT_ASSEMBLY_CONFIG_QUERY_ERROR
            );
        }

        // 3、刷新组件状态
        refreshComponentStatus(roots, configResult.getData());

        for (OrderComponentTreeCompareVO root : roots) {
            createAndCacheComponent(root, null, null, 0);
        }
    }

    /**
     * 构建运行时树
     *
     * 功能：
     * 1、递归转换树结构
     * 2、生成 structureHash
     * 3、初始化默认状态
     */
    private List<OrderComponentTreeCompareVO> buildTree(
            List<WebCadDataComponentGroupReqVO> list) {

        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }

        List<OrderComponentTreeCompareVO> roots = new ArrayList<>();

        for (WebCadDataComponentGroupReqVO node : list) {

            OrderComponentTreeCompareVO root = convertNode(node);

            // 仅给根节点计算 hash
            root.setStructureHash(
                    DigestUtil.sha256Hex(
                            buildStructureString(root)
                    )
            );

            roots.add(root);
        }

        return roots;
    }

    /**
     * 递归转换节点
     */
    private OrderComponentTreeCompareVO convertNode(
            WebCadDataComponentGroupReqVO node) {

        OrderComponentTreeCompareVO vo =
                new OrderComponentTreeCompareVO();

        vo.setId(node.getGroupId() == null
                ? null
                : node.getGroupId());

        vo.setName(node.getName());

        // 尺寸
        if (ObjectUtil.isNotNull(node.getBoxSize())) {

            vo.setWidth(node.getBoxSize().getWidth());

            vo.setHeight(node.getBoxSize().getHeight());

            vo.setDepth(node.getBoxSize().getDepth());
        }

        List<OrderComponentTreeCompareVO> children =
                new ArrayList<>();

        if (CollUtil.isNotEmpty(node.getChildren())) {

            for (WebCadDataComponentGroupReqVO child :
                    node.getChildren()) {

                children.add(convertNode(child));
            }
        }

        vo.setChildren(children);

        return vo;
    }

    /**
     * 递归生成 structureHash
     *
     * @return 当前节点结构字符串
     */
    private String buildStructureString(
            OrderComponentTreeCompareVO node) {

        StringBuilder sb = new StringBuilder();

        sb.append(node.getName());

        List<OrderComponentTreeCompareVO> children =
                node.getChildren();

        if (CollUtil.isNotEmpty(children)) {

            children.sort(
                    Comparator.comparing(
                            OrderComponentTreeCompareVO::getName
                    )
            );

            sb.append("(");

            for (int i = 0; i < children.size(); i++) {

                if (i > 0) {
                    sb.append(",");
                }

                sb.append(
                        buildStructureString(
                                children.get(i)
                        )
                );
            }

            sb.append(")");
        }

        return sb.toString();
    }

    /**
     * 刷新组件树状态
     *
     * 规则：
     * 1、当前节点配置 process=false => 不加工
     * 2、任意直接子节点为“不加工” => 当前节点部分加工
     * 3、否则 => 未加工
     */
    private void refreshComponentStatus(
            List<OrderComponentTreeCompareVO> roots,
            List<AssemblyConfigRespDTO> configList) {

        if (CollUtil.isEmpty(roots)
                || CollUtil.isEmpty(configList)) {
            return;
        }

        // 顶级配置索引
        Map<String, AssemblyConfigRespDTO> configMap =
                configList.stream()
                        .collect(Collectors.toMap(
                                item -> buildKey(
                                        item.getComponentName(),
                                        item.getStructureHash()
                                ),
                                Function.identity(),
                                (a, b) -> a
                        ));

        for (OrderComponentTreeCompareVO root : roots) {

            String rootKey = buildKey(
                    root.getName(),
                    root.getStructureHash()
            );

            AssemblyConfigRespDTO config = configMap.get(rootKey);

            // 没有配置，默认整棵树未加工
            if (config == null) {
                continue;
            }

            // 顶级是否不加工
            if (Boolean.TRUE.equals(config.getNotProcess())) {

                root.setProcessStatus(
                        OrderComponentProcessStatusEnum.NOT_PROCESS.getStatus()
                );
            }

            // 反序列化配置树
            OrderComponentTreeCompareVO configTree =
                    JSONUtil.toBean(
                            JsonUtils.unzipString(
                                    config.getStructureJson()
                            ),
                            OrderComponentTreeCompareVO.class
                    );

            // 递归同步状态
            refreshStatus(root, configTree);
        }
    }

    /**
     * 递归同步不加工状态
     */
    private void refreshStatus(
            OrderComponentTreeCompareVO actualNode,
            OrderComponentTreeCompareVO configNode) {

        List<OrderComponentTreeCompareVO> actualChildren =
                actualNode.getChildren();

        List<OrderComponentTreeCompareVO> configChildren =
                configNode.getChildren();

        if (CollUtil.isEmpty(actualChildren)
                || CollUtil.isEmpty(configChildren)) {

            return;
        }

        int size = Math.min(
                actualChildren.size(),
                configChildren.size()
        );

        for (int i = 0; i < size; i++) {

            OrderComponentTreeCompareVO actualChild =
                    actualChildren.get(i);

            OrderComponentTreeCompareVO configChild =
                    configChildren.get(i);

            // 子节点不加工
            if (!configChild.isProcess()) {

                actualChild.setProcessStatus(
                        OrderComponentProcessStatusEnum.NOT_PROCESS.getStatus()
                );
            }

            refreshStatus(actualChild, configChild);
        }
    }

    /**
     * 构建key
     */
    private String buildKey(String name, String hash) {
        return name + "#" + hash;
    }

    /**
     * 创建部件并缓存
     *
     * @param componentGroupReqVO cad部件数据
     * @param parentId            父级部件id
     * @param topId               顶级部件id
     * @param level               层级从0开始计算
     */
    private void createAndCacheComponent(OrderComponentTreeCompareVO componentGroupReqVO, Long parentId, Long topId, int level) {
        if (ObjectUtil.isNull(parentId)) {
            parentId = OrderComponentDO.PARENT_ID_ROOT;
        }
        Integer cadGroupId = componentGroupReqVO.getId();
        Long componentId = (Long) snowFlakeGenerator.nextId(null);
        // 顶级节点创建过一次后后续继续使用
        if (ObjectUtil.isNull(topId)) {
            topId = componentId;
        }
        OrderComponentDO componentDO = OrderComponentDO.builder()
                .id(componentId)
                .organId(organId)
                .orderId(orderId)
                .pid(parentId)
                .topId(topId)
                .name(componentGroupReqVO.getName())
                .width(componentGroupReqVO.getWidth())
                .height(componentGroupReqVO.getHeight())
                .depth(componentGroupReqVO.getDepth())
                .cadViewFileId(cadViewFileId)
                .processStatus(OrderComponentProcessStatusEnum.UNPROCESSED.getStatus())
                .extraProcessStatus(componentGroupReqVO.getProcessStatus())
                .level(level)
                .build();
        componentDO.setCreateTime(now);
        componentDO.setUpdateTime(now);
        componentDO.setCreator(operatorName);
        componentDO.setUpdater(operatorName);
        componentDO.setDeleted(false);

        // 缓存部件
        componentGroupMap.put(cadGroupId, componentDO);
        // 部件入库
        orderComponentDOS.add(componentDO);
        // 继续处理下级
        if (CollUtil.isNotEmpty(componentGroupReqVO.getChildren())) {
            for (OrderComponentTreeCompareVO children : componentGroupReqVO.getChildren()) {
                createAndCacheComponent(children, componentId, topId, level + 1);
            }
        }
    }

    /**
     * 解析订单信息板材列表
     *
     * @param douleRoomTree
     */
    private void analyzeBlockList(List<WebCadDataDoubleRoomTreeReqVO> douleRoomTree) {
        if (CollUtil.isEmpty(douleRoomTree)) {
            return;
        }
        // 获取自定义板编号配置
        plateNoGenerateConfigVO = customPlateNoGenerateService.getPlateNoGenerateConfig(orderId);

        // 所有房间名
        List<String> roomNames = douleRoomTree.stream().map(WebCadDataDoubleRoomTreeReqVO::getName).collect(Collectors.toList());

        // 查询当前订单的所有房间
        existRoomIdMap = orderBodyMapper.selectMaps(
                        new QueryWrapper<OrderBodyDO>()
                                .select("room_name,MIN(room_id) as room_id")
                                .eq(ORDER_ID_FIELD, orderId)
                                .eq(ORGAN_ID_FIELD, organId)
                                .eq(DELETED_FIELD, false)
                                .in(ROOM_NAME_FIELD, roomNames)
                                .groupBy(ROOM_NAME_FIELD)).stream()
                .collect(Collectors.toMap(
                        m -> String.valueOf(m.get(ROOM_NAME_FIELD)),
                        m -> ((Number) m.get("room_id")).longValue()
                ));

        for (WebCadDataDoubleRoomTreeReqVO room : douleRoomTree) {
            // 房间名
            String roomName = room.getName();

            // 同步房间直接使用roomId
            Long roomId;
            Long existRoomId = existRoomIdMap.get(roomName);
            if (ObjectUtil.isNotNull(existRoomId)) {
                roomId = existRoomId;
            } else {
                roomId = (Long) snowFlakeGenerator.nextId(null);
                existRoomIdMap.put(roomName, roomId);
            }

            // 柜体列表
            List<WebCadDataDoubleRoomTreeReqVO.ChildrenDTO> roomBoxs = room.getChildren();
            for (WebCadDataDoubleRoomTreeReqVO.ChildrenDTO box : roomBoxs) {
                String boxName = box.getName();

                // 柜体下的板件
                List<WebCadDataBlockReqVO> listBody = box.getBlockList();

                String roomNameKey = roomName + boxName;
                // 获取缓存中的成本数量
                Integer count = roomBodyMap.get(roomNameKey);

                // 成倍创建
                multiCreateBodyPlate(count, roomId, roomName, boxName, listBody);
            }
        }
        orderPlateBatchInsertWithinThreshold(plateDOS, true);
    }

    /**
     * 成倍创建柜体板件
     *
     * @param count
     * @param roomId
     * @param roomName
     * @param listBody
     */
    private void multiCreateBodyPlate(Integer count, Long roomId, String roomName, String boxName, List<WebCadDataBlockReqVO> listBody) {
        for (int i = 0; i < count; i++) {
            Long bodyId = (Long) snowFlakeGenerator.nextId(null);
            OrderBodyDO orderBodyDO = OrderBodyDO.builder().id(bodyId).orderId(orderId).organId(organId)
                    .roomId(roomId).roomName(roomName).name(boxName).width(0.0).height(0.0)
                    .depth(0.0).multiNum(count).multiSort(i + 1).plateNum(0).unregularNum(0).filename("无").remark("无").deleted(false).build();
            orderBodyDO.setCreator(operatorName);
            orderBodyDO.setUpdater(operatorName);
            orderBodyDO.setCreateTime(now);
            orderBodyDO.setUpdateTime(now);

            // 缓存成倍的柜体信息
            String multiRoomBodyName = roomName + boxName + i;
            orderBodyDO.setMultiRoomBodyName(multiRoomBodyName);
            bodyMap.put(multiRoomBodyName, orderBodyDO);

            // 达到阈值了不再创建板件
            if (plateNumReachThreshold) {
                continue;
            }

            for (WebCadDataBlockReqVO block : listBody) {
                if (!plateNumReachThreshold) {
                    // 创建板件相关数据：order_plate、order_item、es.orderModels
                    createPlate(orderBodyDO, block);
                } else {
                    // 达到阈值了跳出循环，不再创建板件
                    break;
                }
            }
        }
    }

    /**
     * 创建板件数据
     *
     * @param orderBodyDO
     * @param block
     */
    private void createPlate(OrderBodyDO orderBodyDO, WebCadDataBlockReqVO block) {
        // 是否异形
        Boolean isSpecialShape = block.isSpecialShape();
        // 是否侧面造型
        boolean sideModel = block.isSideModel();
        // 是否有二维刀路
        boolean has2DModel = block.isHas2DModel();
        // 是否有侧面二维刀路
        boolean hasSide2DModel = block.isHasSide2DModel();
        // 是否造型
        boolean isModel = block.isModel() || sideModel || has2DModel || hasSide2DModel || block.isHas3DModel();
        // 是否排孔
        Integer frontHoleCount = block.getFrontHoleCount();
        Integer backHoleCount = block.getBackHoleCount();

        boolean isRawHole = ((frontHoleCount != null ? frontHoleCount : 0) + (backHoleCount != null ? backHoleCount : 0)) > 0;
        Double area = block.getArea();

        // 柜体长宽深
        orderBodyDO.setWidth(block.getCabinetWidth());
        orderBodyDO.setHeight(block.getCabinetHeight());
        orderBodyDO.setDepth(block.getCabinetDepth());

        Long plateId = (Long) snowFlakeGenerator.nextId(null);
        long plateNo = idWorker.nextId();
        long obtainingTime = idWorker.obtainingTime();

        // 从info.remarks中获取备注相关
        WebCadDataBlockRemarkVO plateRemark = getPlateRemark(block);
        List<String> remarks = plateRemark.getRemarks();
        StringBuilder remark = new StringBuilder();
        if (CollUtil.isNotEmpty(remarks)) {
            for (String remarkStr : remarks) {
                remark.append(remarkStr).append(",");
            }
            remark.deleteCharAt(remark.length() - 1);
        }

        // 累加goods的板件总数
        GoodsDO goodsDO = materialMap.get(block.getGoodsCode());
        goodsDO.setPlateNum(goodsDO.getPlateNum() + 1);
        goodsDO.setArea(goodsDO.getArea() + area);

        String splitHeight = block.getSpliteHeight();
        String spliteWidth = block.getSpliteWidth();
        // 创建order_plate
        PlateDO plateDO = PlateDO.builder()
                .id(plateId)
                .orderId(orderId)
                .name(block.getName())
                .plateNo(String.format("%05d", organId) + obtainingTime + Long.toString(plateNo).substring(2))
                .type(block.getType())
                .goodsId(goodsDO.getId())
                .width(getBigDecimalOrNull(block.getWidth()))
                .height(getBigDecimalOrNull(block.getHeight()))
                .thickness(getBigDecimalOrNull(block.getThickness()))
                .splitHeight(BigDecimal.valueOf(NumberUtil.parseDouble(StringUtils.isNotEmpty(splitHeight) ? splitHeight : String.valueOf(block.getKaiLiaoHeight()))))
                .splitThickness(BigDecimal.valueOf(NumberUtil.parseDouble(block.getSpliteThickness())))
                .splitWidth(BigDecimal.valueOf(NumberUtil.parseDouble(StringUtils.isNotEmpty(spliteWidth) ? spliteWidth : String.valueOf(block.getKaiLiaoWidth()))))
                .area(getBigDecimalOrNull(area))
                .texture(block.getTexture())
                .holeFace(block.getHoleFace())
                .isDoor(block.getIsDoor())
                .openDoorType(block.getOpenDoorType())
                .isSpecialShaped(isSpecialShape)
                .isSculpt(isModel)
                .isSideSculpt(sideModel)
                .is2v(has2DModel)
                .isSide2v(hasSide2DModel)
                .isRowHole(isRawHole)
                .customPlateNoBuilder(new StringBuilder())
                .holeArrange(block.getHoleArrange())
                .unregularPointCount(block.getUnregularPointCount())
                .frontHoleCount(block.getFrontHoleCount())
                .backHoleCount(block.getBackHoleCount())
                .sideHoleCount(block.getSideHoleCount())
                .frontModelCount(block.getFrontModelCount())
                .backModelCount(block.getBackModelCount())
                .offsetX(getBigDecimalOrNull(block.getOffsetX()))
                .offsetY(getBigDecimalOrNull(block.getOffsetY()))
                .isArcAcross(getDefaultFalse(block.getIsArcBase()))
                .moduleTypeId(0L)
                .isOptimized(false)
                .isCutted(0)
                .isCancel(false)
                .filterType(EMPTY_STRING)
                .sealLeft(getBigDecimalOrNull(block.getSealLeft()))
                .sealRight(getBigDecimalOrNull(block.getSealRight()))
                .sealUp(getBigDecimalOrNull(block.getSealUp()))
                .sealDown(getBigDecimalOrNull(block.getSealDown()))
                .reservedEdgeLeft(getBigDecimalOrNull(block.getReservedEdgeLeft()))
                .reservedEdgeRight(getBigDecimalOrNull(block.getReservedEdgeRight()))
                .reservedEdgeUp(getBigDecimalOrNull(block.getReservedEdgeUp()))
                .reservedEdgeDown(getBigDecimalOrNull(block.getReservedEdgeDown()))
                .remark(remark.toString())
                .deleted(false)
                .organId(organId)
                .planId(0L)
                .build();
        plateDO.setCreator(operatorName);
        plateDO.setUpdater(operatorName);
        plateDO.setCreateTime(now);
        plateDO.setUpdateTime(now);

        //批量保存板件
        // 达到板件数量阈值，停止入库，更新全局参数停止后续入库
        currentPlateNum += 1;
        currentPlateArea = currentPlateArea.add(BigDecimal.valueOf(area));
        if (currentPlateNum > cadImportPlateNumThreshold) {
            plateNumReachThreshold = true;
            return;
        }
        // 生成自定义板编号
        plateDO.setRoomName(orderBodyDO.getRoomName());
        plateDO.setBodyName(orderBodyDO.getName());
        generateCustomPlateNo(plateDO, block.getCustBlockNo());

        // 更新房间下板件和异形的数量
        orderBodyDO.setPlateNum(orderBodyDO.getPlateNum() + 1);
        orderBodyDO.setUnregularNum(orderBodyDO.getUnregularNum() + BooleanUtil.toInt(isSpecialShape));

        // 创建造型数据
        OrderModelDO orderModelDO = createModel(plateDO, block, plateRemark);
        plateDO.setModelData(JsonUtils.toJsonString(orderModelDO));

        plateDOS.add(plateDO);
        orderPlateBatchInsertWithinThreshold(plateDOS, false);

        // 遍历加工组信息，生成对应的加工组和item
        analyzeBlockProcessGroup(orderBodyDO, block, plateId);
    }

    /**
     * 创建板件造型es数据
     */
    private OrderModelDO createModel(PlateDO plateDO, WebCadDataBlockReqVO block, WebCadDataBlockRemarkVO plateRemark) {
        // 点阵信息
        WebCadDataBlockReqVO.PointInfoDTO pointInfo = block.getPointInfo();

        OrderModelDO orderModelDO = OrderModelDO.builder()
                .plateId(plateDO.getId())
                .texture(block.getTexture())
                .typographicFace(block.getHoleArrange())
                .openDoorType(block.getOpenDoorType())
                .splitLength(getBigDecimalOrNull(block.getKaiLiaoHeight()))
                .splitWidth(getBigDecimalOrNull(block.getKaiLiaoWidth()))
                .offsetX(getBigDecimalOrNull(block.getOffsetX()))
                .offsetY(getBigDecimalOrNull(block.getOffsetY()))
                .pointDetail(getPointDetail(pointInfo.getPointDetail()))
                .rawPointDetail(getRawPointDetail(pointInfo.getOrgPointDetail()))
                .holeDetail(getHoleDetail(pointInfo.getHoleDetail()))
                .sideHoleDetail(getSideHoleDetail(pointInfo.getSideHoleDetail()))
                .contourDetail(getContourDetail(pointInfo.getModelDetail()))
                .sideDetail(getSideDetail(pointInfo.getSideModelDetail(), pointInfo.getSide2DModelDetail()))
                .sideRemark(getSideRemark(plateRemark.getRemarks()))
                .boardType(block.getBoardTypeName())
                .has2DModel(block.isHas2DModel())
                .has3DModel(block.isHas3DModel())
                .build();

        // 处理备注信息
        JSONObject plateExtraRemark = new JSONObject();
        JSONObject extraObject = plateRemark.getExtraObject();
        if (ObjectUtil.isNotNull(extraObject)) {
            plateExtraRemark.put("extra", extraObject);
        }

        JSONArray drillsObj = plateRemark.getDrillsObj();
        if (ObjectUtil.isNotNull(drillsObj)) {
            plateExtraRemark.put("drills", drillsObj);
        }
        orderModelDO.setPlateExtraRemark(plateExtraRemark);

        orderModelDO.setGoodsId(block.getGoodsCode());
        orderModelDO.setPlateGoodsId(plateDO.getGoodsId());
        return orderModelDO;
    }

    /**
     * 获取生产封边备注
     *
     * @param remarks
     * @return
     */
    private Map<String, String> getSideRemark(List<String> remarks) {
        if (CollUtil.isNotEmpty(remarks)) {
            Map<String, String> sideRemarkMap = new HashMap<>();
            for (int i = 1; i <= remarks.size(); i++) {
                sideRemarkMap.put("Remark" + i, remarks.get(i - 1));
            }
            return sideRemarkMap;
        }
        return null;
    }

    /**
     * 获取板件点明细
     *
     * @param cadPointDetailList
     * @return
     */
    private List<PointDetail> getPointDetail(List<WebCadDataBlockReqVO.PointInfoDTO.PointDetailDTO> cadPointDetailList) {
        if (CollUtil.isNotEmpty(cadPointDetailList)) {
            List<PointDetail> pointDetails = new ArrayList<>();
            for (WebCadDataBlockReqVO.PointInfoDTO.PointDetailDTO cadPointDetail : cadPointDetailList) {
                PointDetail pointDetail = new PointDetail();
                pointDetail.setId(String.valueOf(cadPointDetail.getPointID()));
                pointDetail.setPointX(cadPointDetail.getPointX());
                pointDetail.setPointY(cadPointDetail.getPointY());
                pointDetail.setCurve(cadPointDetail.getCurve());
                pointDetails.add(pointDetail);
            }
            return pointDetails;
        }
        return null;
    }

    /**
     * 获取板件原始点明细
     *
     * @param orgPointDetail
     * @return
     */
    private List<PointDetail> getRawPointDetail(List<WebCadDataBlockReqVO.PointInfoDTO.OrgPointDetailDTO> orgPointDetail) {
        if (CollUtil.isNotEmpty(orgPointDetail)) {
            List<PointDetail> pointDetails = new ArrayList<>();
            for (WebCadDataBlockReqVO.PointInfoDTO.OrgPointDetailDTO cadPointDetail : orgPointDetail) {
                PointDetail pointDetail = new PointDetail();
                pointDetail.setId(String.valueOf(cadPointDetail.getPointID()));
                pointDetail.setPointX(cadPointDetail.getPointX());
                pointDetail.setPointY(cadPointDetail.getPointY());
                pointDetail.setCurve(cadPointDetail.getCurve());
                pointDetail.setSealSize(BigDecimal.valueOf(cadPointDetail.getSealSize()));
                pointDetails.add(pointDetail);
            }
            return pointDetails;
        }
        return null;
    }


    /**
     * 获取板件孔明细列表
     *
     * @param cadHoleDetailList
     * @return
     */
    private List<HoleDetail> getHoleDetail(List<WebCadDataBlockReqVO.PointInfoDTO.HoleDetailDTO> cadHoleDetailList) {
        if (CollUtil.isNotEmpty(cadHoleDetailList)) {
            List<HoleDetail> holeDetails = new ArrayList<>();
            for (WebCadDataBlockReqVO.PointInfoDTO.HoleDetailDTO cadHoleDetail : cadHoleDetailList) {
                HoleDetail holeDetail = new HoleDetail();
                holeDetail.setId(String.valueOf(cadHoleDetail.getHoleID()));
                holeDetail.setFaceType(cadHoleDetail.getFace());
                holeDetail.setHoleType(cadHoleDetail.getHoleType());
                holeDetail.setStartX(cadHoleDetail.getPointX());
                holeDetail.setStartY(cadHoleDetail.getPointY());
                holeDetail.setRadius(cadHoleDetail.getRadius());
                holeDetail.setDepth(cadHoleDetail.getDepth());
                holeDetails.add(holeDetail);
            }
            return holeDetails;
        }
        return null;
    }

    /**
     * 获取侧面孔明细
     *
     * @param cadSideHoleDetailList
     * @return
     */
    private List<HoleDetail> getSideHoleDetail(List<WebCadDataBlockReqVO.PointInfoDTO.SideHoleDetailDTO> cadSideHoleDetailList) {
        if (CollUtil.isNotEmpty(cadSideHoleDetailList)) {
            List<HoleDetail> holeDetails = new ArrayList<>();
            for (WebCadDataBlockReqVO.PointInfoDTO.SideHoleDetailDTO cadHoleDetail : cadSideHoleDetailList) {
                HoleDetail holeDetail = new HoleDetail();
                holeDetail.setId(String.valueOf(cadHoleDetail.getHoleID()));
                holeDetail.setFaceType(cadHoleDetail.getFace());
                holeDetail.setStartX(cadHoleDetail.getPointX());
                holeDetail.setStartY(cadHoleDetail.getPointY());
                holeDetail.setStartZ(cadHoleDetail.getPointZ());
                holeDetail.setRadius(cadHoleDetail.getRadius());
                holeDetail.setEndX(cadHoleDetail.getPointX2());
                holeDetail.setEndY(cadHoleDetail.getPointY2());
                holeDetail.setEndZ(cadHoleDetail.getPointZ());
                holeDetail.setDepth(cadHoleDetail.getDepth());
                holeDetails.add(holeDetail);
            }
            return holeDetails;
        }
        return null;
    }

    /**
     * 获取造型明细
     *
     * @param cadModelDetailList
     * @return
     */
    private List<ModelDetail> getContourDetail(List<WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO> cadModelDetailList) {
        if (CollUtil.isNotEmpty(cadModelDetailList)) {
            List<ModelDetail> modelDetails = new ArrayList<>();
            for (WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO cadModelDetail : cadModelDetailList) {
                ModelDetail modelDetail = new ModelDetail();
                modelDetail.setModelID(cadModelDetail.getModelID());
                modelDetail.setLineID(cadModelDetail.getLineID());
                modelDetail.setFaceType(cadModelDetail.getFace());
                modelDetail.setDepth(cadModelDetail.getDepth());
                modelDetail.setKnifeName(cadModelDetail.getKnifeName());
                modelDetail.setKnifeRadius(cadModelDetail.getKnifeRadius());

                WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO cadOriginModeling = cadModelDetail.getOriginModeling();
                if (ObjectUtil.isNotNull(cadOriginModeling)) {
                    IOriginModelingData originModelingData = new IOriginModelingData();
                    originModelingData.setAddLen(cadOriginModeling.getAddLen());
                    originModelingData.setAddWidth(cadOriginModeling.getAddWidth());
                    originModelingData.setAddDepth(cadOriginModeling.getAddDepth());

                    WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO cadOutline = cadOriginModeling.getOutline();
                    originModelingData.setOutline(getOriginModelingOutlineList(cadOutline));

                    originModelingData.setHoles(getModelingHoles(cadOriginModeling.getHoles()));

                    modelDetail.setOriginModeling(originModelingData);
                }
                List<WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.PointListDTO> pointList = cadModelDetail.getPointList();
                modelDetail.setPointList(getModelDetailPointList(pointList));

                List<WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OffSetListDTO> offSetList = cadModelDetail.getOffSetList();
                modelDetail.setOffSetList(getModelDetailOffsetList(offSetList));

                modelDetails.add(modelDetail);
            }
            return modelDetails;
        }
        return null;
    }

    /**
     * 获取造型偏移量列表
     *
     * @param cadOffSetList
     * @return
     */
    private List<OffSetList> getModelDetailOffsetList(List<WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OffSetListDTO> cadOffSetList) {
        if (CollUtil.isNotEmpty(cadOffSetList)) {
            List<OffSetList> offSetLists = new ArrayList<>();
            for (WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OffSetListDTO cadOffset : cadOffSetList) {
                OffSetList offSet = new OffSetList();
                offSet.setValue(cadOffset.getValue());
                offSet.setDepth(cadOffset.getDeep());
                offSet.setAngle(cadOffset.getAngle());
                offSet.setName(cadOffset.getName());
                offSet.setRadius(cadOffset.getRadius());

                offSetLists.add(offSet);
            }
            return offSetLists;
        }
        return null;
    }

    /**
     * 获取造型点列表
     *
     * @param cadPointList
     * @return
     */
    private List<PointList> getModelDetailPointList(List<WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.PointListDTO> cadPointList) {
        if (CollUtil.isNotEmpty(cadPointList)) {
            List<PointList> pointList = new ArrayList<>();
            for (WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.PointListDTO cadPoint : cadPointList) {
                PointList point = new PointList();
                point.setLineId(cadPoint.getLineID());
                point.setPointId(cadPoint.getPointID());
                point.setPointX(cadPoint.getPointX());
                point.setPointY(cadPoint.getPointY());
                point.setCurve(cadPoint.getCurve());
                point.setDepth(cadPoint.getDepth());
                point.setRadius(cadPoint.getRadius());

                pointList.add(point);
            }
            return pointList;
        }
        return null;
    }

    /**
     * 获取造型轮廓
     *
     * @param cadOutline
     * @return
     */
    private List<IContourData> getOriginModelingOutlineList(WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO cadOutline) {
        if (ObjectUtil.isNull(cadOutline)) {
            return null;
        }
        List<Double> buls = cadOutline.getBuls();
        List<WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.OutlineDTO.PtsDTO> pts = cadOutline.getPts();
        if (CollUtil.isNotEmpty(pts) && CollUtil.isNotEmpty(buls)) {
            List<IContourData> outlineList = new ArrayList<>();
            for (int i = 0; i < pts.size(); i++) {
                IContourData iContourData = new IContourData();
                Point point = new Point();
                point.setX(pts.get(i).getX());
                point.setY(pts.get(i).getY());
                iContourData.setPts(point);
                iContourData.setBuls(buls.get(i));
                outlineList.add(iContourData);
            }
            return outlineList;
        }
        return null;
    }

    /**
     * 获取侧面造型明细
     *
     * @param cadSideModelDetailList
     * @return
     */
    private List<ModelDetail> getSideDetail(List<WebCadDataBlockReqVO.PointInfoDTO.SideModelDetailDTO> cadSideModelDetailList, List<WebCadDataBlockReqVO.PointInfoDTO.SideModelDetailDTO> cadSide2DModelDetail) {
        // 合并侧面2v刀路列表
        if (CollUtil.isNotEmpty(cadSide2DModelDetail)) {
            cadSideModelDetailList.addAll(cadSide2DModelDetail);
        }
        if (CollUtil.isNotEmpty(cadSideModelDetailList)) {
            List<ModelDetail> modelDetails = new ArrayList<>();
            // 侧面造型和侧面2v刀路合并，序号从头计算不使用cad的
            int modelId = 1;
            for (WebCadDataBlockReqVO.PointInfoDTO.SideModelDetailDTO cadSideModelDetail : cadSideModelDetailList) {
                ModelDetail modelDetail = new ModelDetail();
                modelDetail.setModelID(modelId++);
                modelDetail.setLineID(cadSideModelDetail.getLineID());
                modelDetail.setFaceType(cadSideModelDetail.getFace());
                modelDetail.setDepth(cadSideModelDetail.getDepth());
                modelDetail.setKnifeName(cadSideModelDetail.getKnifeName());
                modelDetail.setKnifeRadius(cadSideModelDetail.getKnifeRadius());

                WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO cadSideOriginModeling = cadSideModelDetail.getOriginModeling();
                if (ObjectUtil.isNotNull(cadSideOriginModeling)) {
                    IOriginModelingData originModelingData = new IOriginModelingData();
                    originModelingData.setAddLen(cadSideOriginModeling.getAddLen());
                    originModelingData.setAddWidth(cadSideOriginModeling.getAddWidth());
                    originModelingData.setAddDepth(cadSideOriginModeling.getAddDepth());
                    originModelingData.setThickness(cadSideOriginModeling.getThickness());
                    originModelingData.setDir(cadSideOriginModeling.getDir());
                    originModelingData.setKnifeRadius(cadSideOriginModeling.getKnifeRadius());

                    originModelingData.setOutline(getOriginModelingOutlineList(cadSideOriginModeling.getOutline()));

                    originModelingData.setHoles(getModelingHoles(cadSideOriginModeling.getHoles()));

                    modelDetail.setOriginModeling(originModelingData);
                }
                modelDetail.setPointList(getModelDetailPointList(cadSideModelDetail.getPointList()));

                modelDetail.setOffSetList(getModelDetailOffsetList(cadSideModelDetail.getOffSetList()));

                modelDetails.add(modelDetail);
            }
            return modelDetails;
        }
        return null;
    }

    /**
     * 获取孤岛造型
     *
     * @param cadSideHoles
     * @return
     */
    private List<List<IContourData>> getModelingHoles(List<WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO> cadSideHoles) {
        if (CollUtil.isNotEmpty(cadSideHoles)) {
            List<List<IContourData>> icontourDataList = new ArrayList<>();
            for (WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO sideHole : cadSideHoles) {
                List<IContourData> contourDatas = new ArrayList<>();
                List<WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO.PtsDTO> pts = sideHole.getPts();
                List<Double> buls = sideHole.getBuls();
                for (int i = 0; i < pts.size(); i++) {
                    IContourData iContourData = new IContourData();
                    WebCadDataBlockReqVO.PointInfoDTO.ModelDetailDTO.OriginModelingDTO.HolesDTO.PtsDTO ptsDTO = pts.get(i);
                    Double bul = buls.get(i);
                    iContourData.setBuls(bul);
                    iContourData.setPts(new Point(ptsDTO.getX(), ptsDTO.getY(), 0.0));
                    contourDatas.add(iContourData);
                }
                icontourDataList.add(contourDatas);
            }
            return icontourDataList;
        }
        return null;
    }

    /**
     * 获取板件备注
     *
     * @param block
     * @return
     */
    private WebCadDataBlockRemarkVO getPlateRemark(WebCadDataBlockReqVO block) {
        WebCadDataBlockRemarkVO blockRemark = new WebCadDataBlockRemarkVO();

        List<List<String>> remarks = block.getAllRemarkList();
        if (CollUtil.isEmpty(remarks)) {
            return blockRemark;
        }

        List<String> blockRemarkList = new ArrayList<>();
        for (List<String> remark : remarks) {
            if (CollUtil.isNotEmpty(remark) && remark.size() == 2) {
                String key = remark.get(0);
                String value = remark.get(1);

                if ("#extra".equals(key)) {
                    blockRemark.setExtraObject(JSON.parseObject(value));
                } else if ("#drills".equals(key)) {
                    blockRemark.setDrillsObj(JSON.parseArray(value));
                } else {
                    blockRemarkList.add(value);
                }
            }
        }
        blockRemark.setRemarks(blockRemarkList);
        return blockRemark;
    }

    /**
     * 生成自定义板编号
     *
     * @param plateDO
     * @param cadCustBlockNo cad板号
     */
    private void generateCustomPlateNo(PlateDO plateDO, Integer cadCustBlockNo) {
        if (!isFirstPlate) {
            isFirstPlate = true;
        }
        // 配置不为空时，生成自定义板编号
        if (ObjectUtil.isNotNull(plateNoGenerateConfigVO)) {
            customPlateNoGenerateService.generateCustomPlateNo(orderId, plateDO, plateNoGenerateConfigVO, isFirstPlate);
        }
        // cad的板编号
        if (ObjectUtil.isNotNull(cadCustBlockNo)) {
            plateDO.setCadPlateNo(String.valueOf(cadCustBlockNo));
        }
    }

    /**
     * 解析板件加工组
     *
     * @param orderBodyDO
     * @param block
     * @param plateId
     */
    private void analyzeBlockProcessGroup(OrderBodyDO orderBodyDO, WebCadDataBlockReqVO block, Long plateId) {
        Long bodyId = orderBodyDO.getId();
        Long roomId = orderBodyDO.getRoomId();
        Integer cadViewId = block.getID();

        // 获取缓存的部件id
        Integer cadGroupId = block.getComponentId();
        Long plateComponentId = null;
        if (ObjectUtil.isNotNull(cadGroupId)) {
            OrderComponentDO componentDO = componentGroupMap.get(cadGroupId);
            if (ObjectUtil.isNotNull(componentDO)) {
                plateComponentId = componentDO.getId();

                // 三项都未赋值时，首次绑定所属柜体；后续 block 不覆盖
                if (componentDO.getBodyId() == null
                        && componentDO.getRoomName() == null
                        && componentDO.getBodyName() == null) {
                    componentDO.setBodyId(orderBodyDO.getId());
                    componentDO.setRoomName(orderBodyDO.getRoomName());
                    componentDO.setBodyName(orderBodyDO.getName());
                }
            }
        }

        List<Integer> groupIds = block.getGroupIds();
        if (CollUtil.isNotEmpty(groupIds)) {
            // 加工组缓存存在，一个加工组一个orderGroup+orderItem
            for (Integer groupId : groupIds) {
                WebCadDataGroupInfoReqVO groupInfo = processGroupMap.get(groupId);
                String groupMapKey = orderBodyDO.getMultiRoomBodyName() + groupInfo.getGroupName();
                OrderGroupDO orderGroupDO = orderGroupMap.get(groupMapKey);
                // 房间+柜体+加工组名称存在累加板件和异形数量，不存在新增groupDO和orderGroupMap缓存
                if (ObjectUtil.isNull(orderGroupDO)) {
                    Long orderGroupId = (Long) snowFlakeGenerator.nextId(null);
                    orderGroupDO = OrderGroupDO
                            .builder()
                            .id(orderGroupId)
                            .organId(organId)
                            .orderId(orderId)
                            .bodyId(bodyId)
                            .groupTypeId(groupInfo.getGroupTypeId())
                            .groupTypeName(groupInfo.getGroupTypeName())
                            .name(groupInfo.getGroupTypeName())
                            .width(groupInfo.getWidth())
                            .height(groupInfo.getHeight())
                            .depth(groupInfo.getDepth())
                            .multiNum(0)
                            .plateNum(0)
                            .unregularNum(0)
                            .filename("无")
                            .remark("无")
                            .deleted(false)
                            .build();
                    orderGroupDO.setCreator(operatorName);
                    orderGroupDO.setUpdater(operatorName);
                    orderGroupDO.setCreateTime(now);
                    orderGroupDO.setUpdateTime(now);

                    orderGroupMap.put(groupMapKey, orderGroupDO);
                } else {
                    orderGroupDO.setPlateNum(orderGroupDO.getPlateNum() + 1);
                    orderGroupDO.setUnregularNum(orderGroupDO.getUnregularNum() + BooleanUtil.toInt(block.isSpecialShape()));
                }

                OrderItemDO orderItemDO = OrderItemDO
                        .builder()
                        .id((Long) snowFlakeGenerator.nextId(null))
                        .orderId(orderId)
                        .type(OrderItemTypeEnum.PLATE_ITEM.getStatus())
                        .roomId(roomId)
                        .bodyId(bodyId)
                        .groupId(orderGroupDO.getId())
                        .plateId(plateId)
                        .packageId(0L)
                        .partsId(0L)
                        .num(1.0)
                        .cadViewId(cadViewId)
                        .organId(organId)
                        .build();
                orderItemDOS.add(orderItemDO);
                orderItemBatchInsertWithinThreshold(orderItemDOS, false);
            }
        } else {
            // 没有归属的加工组，只创建一个orderItem
            OrderItemDO orderItemDO = OrderItemDO
                    .builder()
                    .id((Long) snowFlakeGenerator.nextId(null))
                    .orderId(orderId)
                    .type(OrderItemTypeEnum.PLATE_ITEM.getStatus())
                    .roomId(roomId)
                    .bodyId(bodyId)
                    .plateId(plateId)
                    .packageId(0L)
                    .partsId(0L)
                    .num(1.0)
                    .cadViewId(cadViewId)
                    .organId(organId)
                    .build();
            orderItemDOS.add(orderItemDO);
            orderItemBatchInsertWithinThreshold(orderItemDOS, false);
        }

        // 创建部件关联关系
        if(ObjectUtil.isNotNull(plateComponentId)) {
            OrderItemDO orderItemDO = OrderItemDO
                    .builder()
                    .id((Long) snowFlakeGenerator.nextId(null))
                    .orderId(orderId)
                    .type(OrderItemTypeEnum.PLATE_ITEM.getStatus())
                    .roomId(roomId)
                    .bodyId(bodyId)
                    .plateId(plateId)
                    .packageId(0L)
                    .partsId(0L)
                    .num(1.0)
                    .cadViewId(cadViewId)
                    .compId(plateComponentId)
                    .organId(organId)
                    .build();
            orderItemDOS.add(orderItemDO);
            orderItemBatchInsertWithinThreshold(orderItemDOS, false);
        }
    }

    /**
     * 解析配件列表
     *
     * @param parts
     */
    private void analyzeParts(List<WebCadDataDoubleRoomTreeReqVO> parts) {
        if (ObjectUtil.isNull(parts)) {
            return;
        }
        log.debug("====================【cad拆单获取封边配置开始】====================");
        // 获取系统配置-封边对应配置
        getOrgSealEdgeConfig();
        log.debug("====================【cad拆单获取封边配置结束】====================");

        for (WebCadDataDoubleRoomTreeReqVO room : parts) {
            // 房间名
            String roomName = room.getName();

            // 同步房间直接使用roomId
            Long roomId;
            Long existRoomId = existRoomIdMap.get(roomName);

            if (ObjectUtil.isNotNull(existRoomId)) {
                roomId = existRoomId;
            } else {
                // 可能有配件房间柜体不在板件的情况，再查一次
                // 查询当前订单的所有房间
                List<String> roomNames = parts.stream().map(WebCadDataDoubleRoomTreeReqVO::getName).collect(Collectors.toList());

                Map<String, Long> existPartsRoomIdMap = orderBodyMapper.selectMaps(
                                new QueryWrapper<OrderBodyDO>()
                                        .select("room_name,MIN(room_id) as room_id")
                                        .eq(ORDER_ID_FIELD, orderId)
                                        .eq(ORGAN_ID_FIELD, organId)
                                        .eq(DELETED_FIELD, false)
                                        .in(ROOM_NAME_FIELD, roomNames)
                                        .groupBy(ROOM_NAME_FIELD)).stream()
                        .collect(Collectors.toMap(
                                m -> String.valueOf(m.get(ROOM_NAME_FIELD)),
                                m -> ((Number) m.get("room_id")).longValue()
                        ));
                Long existPartRoomId = existPartsRoomIdMap.get(roomName);
                if (ObjectUtil.isNotNull(existPartRoomId)) {
                    roomId = existPartRoomId;
                } else {
                    roomId = (Long) snowFlakeGenerator.nextId(null);
                    existRoomIdMap.put(roomName, roomId);
                }
            }

            // 柜体列表
            List<WebCadDataDoubleRoomTreeReqVO.ChildrenDTO> roomBoxs = room.getChildren();
            for (WebCadDataDoubleRoomTreeReqVO.ChildrenDTO box : roomBoxs) {
                String boxName = box.getName();

                // 柜体下的板件
                List<WebCadDataPartsReqVO.BlockObjectListDTO> listBody = box.getObjList();

                String roomNameKey = roomName + boxName;
                // 获取缓存中的成本数量
                Integer count = roomBodyMap.get(roomNameKey);

                if (ObjectUtil.isNull(count)) {
                    // 房间+柜体不在成倍缓存中, 再检查bodyMap, 再没有创建房间，缓存房间到bodyMap
                    createPartsUnknownBody(roomId, roomName, boxName, listBody);
                } else {
                    multipleCreateProcessGroup(count, roomNameKey, listBody);
                }
            }
        }
        orderPartBatchInsertWithinThreshold(orderPartsDOS, true);
    }

    /**
     * 成倍创建配件加工组和item
     *
     * @param count
     * @param roomNameKey
     * @param listBody
     */
    private void multipleCreateProcessGroup(Integer count, String roomNameKey, List<WebCadDataPartsReqVO.BlockObjectListDTO> listBody) {
        // 成倍数量
        for (int j = 0; j < count; j++) {
            // 获取板材已经创建的柜体，缓存key：房间名+柜体名+成倍序号
            OrderBodyDO orderBodyDO = bodyMap.get(roomNameKey + j);
            if (ObjectUtil.isNull(orderBodyDO)) {
                return;
            }
            // 解析配件的加工组，创建对应的orderItem
            for (WebCadDataPartsReqVO.BlockObjectListDTO part : listBody) {
                analyzePartProcessGroup(orderBodyDO, part);
            }
        }
    }

    /**
     * 创建不需要成倍的房间柜体以及其下的板件
     *
     * @param roomName
     * @param bodyName
     * @param listBody
     */
    private void createPartsUnknownBody(Long roomId, String roomName, String bodyName, List<WebCadDataPartsReqVO.BlockObjectListDTO> listBody) {
        String roomBodyKey = roomName + bodyName;
        OrderBodyDO orderBodyDO = bodyMap.get(roomBodyKey);
        if (ObjectUtil.isNull(orderBodyDO)) {
            Long bodyId = (Long) snowFlakeGenerator.nextId(null);
            orderBodyDO = OrderBodyDO.builder().id(bodyId).orderId(orderId).organId(organId)
                    .roomId(roomId).roomName(roomName).name(bodyName).width(0.0).height(0.0)
                    .depth(0.0).multiNum(1).multiSort(1).plateNum(0).unregularNum(0).filename("无").remark("无").deleted(false).build();
            orderBodyDO.setCreator(operatorName);
            orderBodyDO.setUpdater(operatorName);
            orderBodyDO.setCreateTime(now);
            orderBodyDO.setUpdateTime(now);

            // 缓存成倍的柜体信息
            bodyMap.put(roomBodyKey, orderBodyDO);
        }

        for (WebCadDataPartsReqVO.BlockObjectListDTO part : listBody) {
            analyzePartProcessGroup(orderBodyDO, part);
        }
    }

    private void analyzePartProcessGroup(OrderBodyDO orderBodyDO, WebCadDataPartsReqVO.BlockObjectListDTO part) {
        Long bodyId = orderBodyDO.getId();
        Long roomId = orderBodyDO.getRoomId();
        Integer cadViewId = part.getObjectId();

        // 获取orderPart
        analyzeOrderPart(part);

        // 获取缓存的部件id
        List<Integer> cadGroupIds = part.getComponentIds();
        List<Long> partComponentIds = new ArrayList<>();
        for (Integer cadGroupId : cadGroupIds) {
            OrderComponentDO componentDO = componentGroupMap.get(cadGroupId);
            if (ObjectUtil.isNotNull(componentDO)) {
                partComponentIds.add(componentDO.getId());

                // 三项都未赋值时，首次绑定所属柜体；后续 block 不覆盖
                if (componentDO.getBodyId() == null
                        && componentDO.getRoomName() == null
                        && componentDO.getBodyName() == null) {
                    componentDO.setBodyId(orderBodyDO.getId());
                    componentDO.setRoomName(orderBodyDO.getRoomName());
                    componentDO.setBodyName(orderBodyDO.getName());
                }
            }
        }

        // 遍历加工组信息，生成对应的orderItem
        List<Integer> groupIds = part.getGroupIds();
        if (CollUtil.isNotEmpty(groupIds)) {
            Double partNum = 0.0;
            // 带加工组的配件增加item只记录配件id和数量
            partNum += part.getNum();
            for (Integer groupId : groupIds) {
                WebCadDataGroupInfoReqVO groupInfo = processGroupMap.get(groupId);
                String groupMapKey = orderBodyDO.getMultiRoomBodyName() + groupInfo.getGroupName();
                OrderGroupDO orderGroupDO = orderGroupMap.get(groupMapKey);
                // 房间+柜体+加工组名称存在累加配件item，不存在新增groupDO和orderGroupMap缓存
                if (ObjectUtil.isNull(orderGroupDO)) {
                    Long orderGroupId = (Long) snowFlakeGenerator.nextId(null);
                    orderGroupDO = OrderGroupDO
                            .builder()
                            .id(orderGroupId)
                            .organId(organId)
                            .orderId(orderId)
                            .bodyId(bodyId)
                            .groupTypeId(groupInfo.getGroupTypeId())
                            .groupTypeName(groupInfo.getGroupTypeName())
                            .name(groupInfo.getGroupTypeName())
                            .width(groupInfo.getWidth())
                            .height(groupInfo.getHeight())
                            .depth(groupInfo.getDepth())
                            .multiNum(0)
                            .plateNum(0)
                            .unregularNum(0)
                            .filename("无")
                            .remark("无")
                            .deleted(false)
                            .build();
                    orderGroupDO.setCreator(operatorName);
                    orderGroupDO.setUpdater(operatorName);
                    orderGroupDO.setCreateTime(now);
                    orderGroupDO.setUpdateTime(now);

                    orderGroupMap.put(groupMapKey, orderGroupDO);
                }

                OrderItemDO orderItemDO = OrderItemDO
                        .builder()
                        .id((Long) snowFlakeGenerator.nextId(null))
                        .orderId(orderId)
                        .type(OrderItemTypeEnum.PARTS_ITEM.getStatus())
                        .roomId(roomId)
                        .bodyId(bodyId)
                        .groupId(orderGroupDO.getId())
                        .partsId(part.getImesPartsId())
                        .packageId(0L)
                        .plateId(0L)
                        .num(part.getNum())
                        .cadViewId(cadViewId)
                        .organId(organId)
                        .build();
                orderItemDOS.add(orderItemDO);
                orderItemBatchInsertWithinThreshold(orderItemDOS, false);
            }
            OrderItemDO orderItemDO = OrderItemDO
                    .builder()
                    .id((Long) snowFlakeGenerator.nextId(null))
                    .orderId(orderId)
                    .type(OrderItemTypeEnum.PARTS_ITEM.getStatus())
                    .roomId(roomId)
                    .bodyId(bodyId)
                    .partsId(part.getImesPartsId())
                    .packageId(0L)
                    .plateId(0L)
                    .num(partNum)
                    .cadViewId(cadViewId)
                    .groupId(0L) // 用于统计加工组下配件的总数
                    .organId(organId)
                    .build();
            orderItemDOS.add(orderItemDO);
            orderItemBatchInsertWithinThreshold(orderItemDOS, false);
        } else {
            OrderItemDO orderItemDO = OrderItemDO
                    .builder()
                    .id((Long) snowFlakeGenerator.nextId(null))
                    .orderId(orderId)
                    .type(OrderItemTypeEnum.PARTS_ITEM.getStatus())
                    .roomId(roomId)
                    .bodyId(bodyId)
                    .partsId(part.getImesPartsId())
                    .packageId(0L)
                    .plateId(0L)
                    .num(part.getNum())
                    .cadViewId(cadViewId)
                    .organId(organId)
                    .build();
            orderItemDOS.add(orderItemDO);
            orderItemBatchInsertWithinThreshold(orderItemDOS, false);
        }

        // 创建部件的关联关系
        if(CollUtil.isNotEmpty(partComponentIds)) {
            for (Long partComponentId : partComponentIds) {
                OrderItemDO orderItemDO = OrderItemDO
                        .builder()
                        .id((Long) snowFlakeGenerator.nextId(null))
                        .orderId(orderId)
                        .type(OrderItemTypeEnum.PARTS_ITEM.getStatus())
                        .roomId(roomId)
                        .bodyId(bodyId)
                        .partsId(part.getImesPartsId())
                        .packageId(0L)
                        .plateId(0L)
                        .num(part.getNum())
                        .compId(partComponentId)
                        .cadViewId(cadViewId)
                        .organId(organId)
                        .build();
                orderItemDOS.add(orderItemDO);
                orderItemBatchInsertWithinThreshold(orderItemDOS, false);
            }
        }
    }

    /**
     * 分组配件解析出orderParts，id回显到cad配件
     *
     * @param part
     */
    private void analyzeOrderPart(WebCadDataPartsReqVO.BlockObjectListDTO part) {
        boolean composite = part.getIsComposite();
        String partType = part.getType();
        String factory = part.getFactory();
        String model = part.getModel();
        String spec = part.getSpec();
        String height = part.getHeight();
        String width = part.getWidth();
        String thickness = part.getThickness();
        String unit = part.getUnit();
        String brand = part.getBrand();

        // 分类配件的键组合
        String groupKey = composite + partType + factory + model + spec + scale3(height) + scale3(width) + scale3(thickness) + unit + brand;

        Long orderPartId = orderPartMap.get(groupKey);
        if (ObjectUtil.isNull(orderPartId)) {
            Long partId = (Long) snowFlakeGenerator.nextId(null);

            String color = null;
            String material = null;
            if (PART_CATEGORY_ONE.equals(partType)) {
                WebCadDataPartsReqVO.BlockObjectListDTO.GroupDataDTO partGroupData = part.getGroupData();
                color = partGroupData.getColor();
                material = partGroupData.getMaterial();
                // 封边条宽度从系统配置中获取宽度对应
                sealEdgeWidthMatch(part);
                // 规格可能修改，再获取一次
                spec = part.getSpec();
            } else {
                spec = getFieldFromSpec(part.getSpec(), 0);
                material = getFieldFromSpec(part.getSpec(), 1);
                color = getFieldFromSpec(part.getSpec(), 2);
            }

            String type = changePartType(partType, part.getIsComposite());
            // todo remark、备注信息到es
            OrderPartsDO orderPartsDO = OrderPartsDO.builder()
                    .id(partId)
                    .orderId(orderId)
                    .color(getEmpty(color))
                    .category(type)
                    .type(type)
                    .goodsId(EMPTY_STRING)
                    .length(NumberUtil.parseDouble(part.getHeight()))
                    .width(NumberUtil.parseDouble(part.getWidth()))
                    .thickness(NumberUtil.parseDouble(part.getThickness()))
                    .isComposite(part.getIsComposite())
                    .name(part.getName())
                    .material(getEmpty(material))
                    .model(getEmpty(model))
                    .spec(getEmpty(spec))
                    .brand(getEmpty(brand))
                    .factory(getEmpty(factory))
                    .unit(part.getUnit())
                    .price(0.0)
                    .subparts(EMPTY_STRING)
                    .remark(getEmpty(part.getRemark()))
                    .organId(organId)
                    .deleted(false)
                    .build();
            orderPartsDO.setCreator(operatorName);
            orderPartsDO.setUpdater(operatorName);
            orderPartsDO.setCreateTime(now);
            orderPartsDO.setUpdateTime(now);
            orderPartsDOS.add(orderPartsDO);
            orderPartBatchInsertWithinThreshold(orderPartsDOS, false);

            // orderpart id回显到配件
            part.setImesPartsId(partId);

            orderPartMap.put(groupKey, partId);
        } else {
            // orderpart id回显到配件
            part.setImesPartsId(orderPartId);
        }
    }

    /**
     * 保留三位小数展示
     *
     * @param value
     * @return
     */
    private String scale3(String value) {
        if (StringUtils.isEmpty(value)) return value;
        try {
            BigDecimal bd = new BigDecimal(value);
            bd = bd.setScale(3, RoundingMode.HALF_UP);
            return bd.toPlainString();
        } catch (Exception e) {
            // 不是数字就原样返回
            return value;
        }
    }

    /**
     * 从规格中获取属性，例：material、color
     *
     * @return
     */
    private String getFieldFromSpec(String spec, int index) {
        if (StringUtils.isNotEmpty(spec)) {
            String[] specSplit = spec.split("\\|");
            if (specSplit.length > index) {
                return specSplit[index];
            }
        }
        return null;
    }


    /**
     * 封边条宽度匹配，获取厚度
     *
     * @param part
     */
    private void sealEdgeWidthMatch(WebCadDataPartsReqVO.BlockObjectListDTO part) {
        // 获取宽度、厚度
        WebCadDataPartsReqVO.BlockObjectListDTO.GroupDataDTO partGroupData = part.getGroupData();

        if (ObjectUtil.isNotNull(partGroupData)) {
            part.setThickness(String.valueOf(partGroupData.getThickness()));
            Integer thickness = partGroupData.getThickness();

            // 是否匹配上系统配置的封边条宽度配置，对应上了需要同步修改spec的中的内容
            boolean sealEdgeConfigMatch = false;
            String matchWidth = null;
            for (SystemConfigSealEdgeRespDTO configSealEdgeRespDTO : sealEdgeConfigList) {
                // 宽度处于配置范围，宽取值作为最终配件宽度
                if (thickness >= configSealEdgeRespDTO.getWidthMin() && thickness <= configSealEdgeRespDTO.getWidthMax()) {
                    matchWidth = String.valueOf(configSealEdgeRespDTO.getWidthValue());
                    part.setWidth(matchWidth);
                    sealEdgeConfigMatch = true;
                    break;
                }
            }
            if (sealEdgeConfigMatch) {
                part.setSpec(modifySealEdgeSpec(part.getSpec(), matchWidth));
            } else {
                // 没有匹配的对应直接使用板厚做宽度
                part.setWidth(String.valueOf(thickness));
            }
        }
    }

    /**
     * 根据对应的宽度修改spec规格中的第二段宽度
     *
     * @param spec
     * @param width
     * @return
     */
    private String modifySealEdgeSpec(String spec, String width) {
        if (StringUtils.isNotEmpty(spec)) {
            String[] specSplit = spec.split("-");
            // 例：15mm-18mm
            if (specSplit.length == 2) {
                return specSplit[0] + "-" + width + "mm";
            }
            // 例：9mm-18mm-经典檀木
            if (specSplit.length == 3) {
                return specSplit[0] + "-" + width + "mm-" + specSplit[2];
            }
        }
        return spec;
    }

    /**
     * value为空返回EMPTY_STRING
     *
     * @param value
     * @return
     */
    private String getEmpty(String value) {
        return StringUtils.defaultIfEmpty(value, EMPTY_STRING);
    }

    /**
     * value为空返回0.0
     *
     * @param value
     * @return
     */
    private double getZeroDouble(Double value) {
        return ObjectUtil.defaultIfNull(value, 0.0);
    }

    /**
     * value为空返回false
     *
     * @param value
     * @return
     */
    private Boolean getDefaultFalse(Boolean value) {
        return ObjectUtil.defaultIfNull(value, false);
    }

    /**
     * value不可转返回null
     *
     * @param value
     * @return
     */
    private BigDecimal getBigDecimalOrNull(Double value) {
        try {
            return BigDecimal.valueOf(value);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 配件类型转换
     *
     * @param objectType
     * @param isComposite
     * @return
     */
    private String changePartType(String objectType, boolean isComposite) {
        if (PART_CATEGORY_ONE.equals(objectType)) {
            return PART_CATEGORY_ONE;
        } else {
            if (isComposite) {
                return PART_CATEGORY_THREE;
            } else {
                return PART_CATEGORY_TWO;
            }
        }
    }

    /**
     * 每达到阈值就批量插入一次
     *
     * @param baseBatchMapper
     * @param list
     * @param isLast          是否最后一次插入，不是就需要清理list
     */
    private void orderInfoBatchInsertWithinThreshold(BaseBatchMapper baseBatchMapper, List list, boolean isLast) {
        // 达到批量的阈值就做一次插入
        if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) {
            baseBatchMapper.insertBatchSomeColumn(list);
            if (!isLast) {
                // 存储完成清理 list
                list.clear();
            }
        }
    }

    private void orderPlateBatchInsertWithinThreshold(List<PlateDO> list, boolean isLast) {
        // 达到批量的阈值就做一次插入
        if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) {
            String sql = "INSERT INTO order_plate  (id,organ_id,order_id,name,plate_no,type,goods_id,width,height,thickness,split_width,split_height,split_thickness,seal_left,seal_right,seal_up,seal_down,area,texture,hole_face,hole_arrange,unregular_point_count,front_hole_count,back_hole_count,side_hole_count,front_model_count,back_model_count,is_door,open_door_type,offset_x,offset_y,is_arc_across,module_type_id,is_special_shaped,is_sculpt,is_side_sculpt,is_2v,is_side_2v,is_row_hole,is_optimized,is_cutted,filter_type,remark,is_cancel,deleted,custom_plate_no,create_time,update_time,creator,updater, cad_plate_no, model_data, reserved_edge_left, reserved_edge_right, reserved_edge_up, reserved_edge_down) VALUES    (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
            jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int index) throws SQLException {
                    PlateDO item = list.get(index);
                    ps.setLong(1, item.getId());
                    ps.setLong(2, item.getOrganId());
                    ps.setLong(3, item.getOrderId());
                    ps.setString(4, item.getName());
                    ps.setString(5, item.getPlateNo());
                    ps.setInt(6, item.getType());
                    ps.setLong(7, item.getGoodsId());
                    setSqlDefaultBigDecimal(ps, 8, item.getWidth());
                    setSqlDefaultBigDecimal(ps, 9, item.getHeight());
                    setSqlDefaultBigDecimal(ps, 10, item.getThickness());
                    setSqlDefaultBigDecimal(ps, 11, item.getSplitWidth());
                    setSqlDefaultBigDecimal(ps, 12, item.getSplitHeight());
                    setSqlDefaultBigDecimal(ps, 13, item.getSplitThickness());
                    setSqlDefaultBigDecimal(ps, 14, item.getSealLeft());
                    setSqlDefaultBigDecimal(ps, 15, item.getSealRight());
                    setSqlDefaultBigDecimal(ps, 16, item.getSealUp());
                    setSqlDefaultBigDecimal(ps, 17, item.getSealDown());
                    setSqlDefaultBigDecimal(ps, 18, item.getArea());
                    ps.setInt(19, item.getTexture());
                    ps.setInt(20, item.getHoleFace());
                    ps.setInt(21, item.getHoleArrange());
                    ps.setInt(22, item.getUnregularPointCount());
                    ps.setInt(23, item.getFrontHoleCount());
                    ps.setInt(24, item.getBackHoleCount());
                    ps.setInt(25, item.getSideHoleCount());
                    ps.setInt(26, item.getFrontModelCount());
                    ps.setInt(27, item.getBackModelCount());
                    ps.setBoolean(28, item.getIsDoor());
                    ps.setInt(29, item.getOpenDoorType());
                    ps.setBigDecimal(30, item.getOffsetX());
                    ps.setBigDecimal(31, item.getOffsetY());
                    ps.setBoolean(32, item.getIsArcAcross());
                    ps.setLong(33, item.getModuleTypeId());
                    ps.setBoolean(34, item.getIsSpecialShaped());
                    ps.setBoolean(35, item.getIsSculpt());
                    ps.setBoolean(36, item.getIsSideSculpt());
                    ps.setBoolean(37, item.getIs2v());
                    ps.setBoolean(38, item.getIsSide2v());
                    ps.setBoolean(39, item.getIsRowHole());
                    ps.setBoolean(40, item.getIsOptimized());
                    ps.setInt(41, item.getIsCutted());
                    ps.setString(42, item.getFilterType());
                    ps.setString(43, item.getRemark());
                    ps.setBoolean(44, item.getIsCancel());
                    ps.setBoolean(45, item.getDeleted());
                    ps.setString(46, item.getCustomPlateNo());
                    ps.setTimestamp(47, Timestamp.valueOf(item.getCreateTime()));
                    ps.setTimestamp(48, Timestamp.valueOf(item.getUpdateTime()));
                    ps.setString(49, item.getCreator());
                    ps.setString(50, item.getUpdater());
                    ps.setString(51, item.getCadPlateNo());

                    String modelData = item.getModelData();
                    if (StringUtils.isNotEmpty(modelData)) {
                        ps.setString(52, JsonUtils.zipString(modelData));
                    } else {
                        ps.setNull(52, Types.BLOB);
                    }
                    setSqlDefaultBigDecimal(ps, 53, item.getReservedEdgeLeft());
                    setSqlDefaultBigDecimal(ps, 54, item.getReservedEdgeRight());
                    setSqlDefaultBigDecimal(ps, 55, item.getReservedEdgeUp());
                    setSqlDefaultBigDecimal(ps, 56, item.getReservedEdgeDown());
                }

                @Override
                public int getBatchSize() {
                    return list.size();
                }
            });


            if (!isLast) {
                // 存储完成清理 list
                clearPlate();
            }
        }
    }

    private void clearPlate() {
        plateDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER);
    }

    private void orderItemBatchInsertWithinThreshold(List<OrderItemDO> list, boolean isLast) {
        // 达到批量的阈值就做一次插入
        if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) {

            String sql = "INSERT INTO order_item (id,order_id,type,room_id,body_id,package_id,group_id,plate_id,parts_id,num,organ_id,comp_id,cad_view_id) VALUES    (?,?,?,?,?,?,?,?,?,?,?,?,?)";
            jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int index) throws SQLException {
                    OrderItemDO item = list.get(index);
                    ps.setLong(1, item.getId());
                    ps.setLong(2, item.getOrderId());
                    ps.setInt(3, item.getType());
                    ps.setLong(4, item.getRoomId());
                    ps.setLong(5, item.getBodyId());
                    ps.setLong(6, item.getPackageId());
                    if (item.getGroupId() != null) {
                        ps.setLong(7, item.getGroupId());
                    } else {
                        ps.setNull(7, Types.BIGINT);
                    }
                    ps.setLong(8, item.getPlateId());
                    ps.setLong(9, item.getPartsId());
                    ps.setDouble(10, item.getNum());
                    ps.setLong(11, item.getOrganId());
                    if (item.getCompId() != null) {
                        ps.setLong(12, item.getCompId());
                    } else {
                        ps.setNull(12, Types.BIGINT);
                    }
                    if (item.getCadViewId() != null) {
                        ps.setLong(13, item.getCadViewId());
                    } else {
                        ps.setNull(13, Types.INTEGER);
                    }
                }

                @Override
                public int getBatchSize() {
                    return list.size();
                }
            });


            if (!isLast) {
                // 存储完成清理 list
                clearItem();
            }
        }
    }

    private void clearItem() {
        orderItemDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER);
    }

    private void orderPartBatchInsertWithinThreshold(List<OrderPartsDO> list, boolean isLast) {
        // 达到批量的阈值就做一次插入
        if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) {

            String sql = "INSERT INTO order_parts (id,organ_id,order_id,goods_id,name,color,material,category,type,width,length,thickness,model,spec,brand,factory,unit,price,is_composite,subparts,remark,deleted,create_time,update_time,creator,updater) VALUES    (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
            jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int index) throws SQLException {
                    OrderPartsDO item = list.get(index);
                    ps.setLong(1, item.getId());
                    ps.setLong(2, item.getOrganId());
                    ps.setLong(3, item.getOrderId());
                    ps.setString(4, item.getGoodsId());
                    ps.setString(5, item.getName());
                    ps.setString(6, item.getColor());
                    ps.setString(7, item.getMaterial());
                    ps.setString(8, item.getCategory());
                    ps.setString(9, item.getType());
                    ps.setDouble(10, item.getWidth());
                    ps.setDouble(11, item.getLength());
                    ps.setDouble(12, item.getThickness());
                    ps.setString(13, item.getModel());
                    ps.setString(14, item.getSpec());
                    ps.setString(15, item.getBrand());
                    ps.setString(16, item.getFactory());
                    ps.setString(17, item.getUnit());
                    ps.setDouble(18, item.getPrice());
                    ps.setBoolean(19, item.getIsComposite());
                    ps.setString(20, item.getSubparts());
                    ps.setString(21, item.getRemark());
                    ps.setBoolean(22, item.getDeleted());
                    ps.setTimestamp(23, Timestamp.valueOf(item.getCreateTime()));
                    ps.setTimestamp(24, Timestamp.valueOf(item.getUpdateTime()));
                    ps.setString(25, item.getCreator());
                    ps.setString(26, item.getUpdater());
                }

                @Override
                public int getBatchSize() {
                    return list.size();
                }
            });


            if (!isLast) {
                // 存储完成清理 list
                clearPart();
            }
        }
    }

    private void clearPart() {
        orderPartsDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER);
    }

    private void orderComponentBatchInsertWithinThreshold(List<OrderComponentDO> list, boolean isLast) {
        // 达到批量的阈值就做一次插入
        if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) {

            String sql = """
                        INSERT INTO order_component (
                            id,
                            order_id,
                            organ_id,
                            pid,
                            top_id,
                            name,
                            width,
                            height,
                            depth,
                            spec,
                            cad_view_file_id,
                            process_status,
                            remark,
                            creator,
                            create_time,
                            updater,
                            update_time,
                            deleted,
                            level,
                            extra_process_status,
                            body_id,
                            body_name,
                            room_name
                        ) VALUES (
                            ?,?,?,?,?,?,
                            ?,?,?,?,
                            ?,?,?,?,
                            ?,?,?,
                            ?,?,?,
                            ?,?,?
                        )
                    """;
            jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int index) throws SQLException {
                    OrderComponentDO item = list.get(index);
                    ps.setLong(1, item.getId());
                    ps.setLong(2, item.getOrderId());
                    ps.setLong(3, item.getOrganId());
                    ps.setLong(4, item.getPid());
                    ps.setLong(5, item.getTopId());
                    ps.setString(6, item.getName());
                    if (item.getWidth() != null) {
                        ps.setDouble(7, item.getWidth());
                    } else {
                        ps.setNull(7, Types.DOUBLE);
                    }
                    if (item.getHeight() != null) {
                        ps.setDouble(8, item.getHeight());
                    } else {
                        ps.setNull(8, Types.DOUBLE);
                    }
                    if (item.getDepth() != null) {
                        ps.setDouble(9, item.getDepth());
                    } else {
                        ps.setNull(9, Types.DOUBLE);
                    }

                    ps.setString(10, item.getSpec());
                    ps.setLong(11, item.getCadViewFileId());
                    ps.setInt(12, item.getProcessStatus());
                    ps.setString(13, item.getRemark());
                    ps.setString(14, item.getCreator());
                    ps.setTimestamp(15, Timestamp.valueOf(item.getCreateTime()));
                    ps.setString(16, item.getUpdater());
                    ps.setTimestamp(17, Timestamp.valueOf(item.getUpdateTime()));
                    ps.setBoolean(18, item.getDeleted());
                    ps.setInt(19, item.getLevel());
                    Integer extraProcessStatus = item.getExtraProcessStatus();
                    if (extraProcessStatus != null) {
                        ps.setInt(20, extraProcessStatus);
                    } else {
                        ps.setNull(20, Types.INTEGER);
                    }
                    ps.setLong(21, item.getBodyId());
                    ps.setString(22, item.getBodyName());
                    ps.setString(23, item.getRoomName());
                }

                @Override
                public int getBatchSize() {
                    return list.size();
                }
            });


            if (!isLast) {
                // 存储完成清理 list
                clearComponent();
            }
        }
    }

    private void clearComponent() {
        orderComponentDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER);
    }

    /**
     * 保存数据
     * orderBody、orderGroup
     */
    private void saveData() {
        // 分成1000一组
        List<List<OrderBodyDO>> bodyList = batchProcess(bodyMap.values().stream().toList());
        List<List<OrderGroupDO>> groupList = batchProcess(orderGroupMap.values().stream().toList());

        // 保存
        saveBody(bodyList);
        saveGroup(groupList);
    }

    /**
     * 插入柜体
     *
     * @param bodyList
     */
    private void saveBody(List<List<OrderBodyDO>> bodyList) {
        for (List<OrderBodyDO> bodys : bodyList) {
            String sql = "INSERT INTO order_body (id, organ_id, order_id, room_id, room_name, name, width, height, depth, multi_num, plate_num, unregular_num, filename, remark, deleted,create_time,update_time,creator,updater, cad_view_file_id, multi_sort) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
            jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int index) throws SQLException {
                    OrderBodyDO orderBodyDO = bodys.get(index);
                    ps.setLong(1, orderBodyDO.getId());
                    ps.setLong(2, orderBodyDO.getOrganId());
                    ps.setLong(3, orderBodyDO.getOrderId());
                    ps.setLong(4, orderBodyDO.getRoomId());
                    ps.setString(5, orderBodyDO.getRoomName());
                    ps.setString(6, orderBodyDO.getName());
                    ps.setDouble(7, orderBodyDO.getWidth());
                    ps.setDouble(8, orderBodyDO.getHeight());
                    ps.setDouble(9, orderBodyDO.getDepth());
                    ps.setInt(10, orderBodyDO.getMultiNum());
                    ps.setInt(11, orderBodyDO.getPlateNum());
                    ps.setInt(12, orderBodyDO.getUnregularNum());
                    ps.setString(13, orderBodyDO.getFilename());
                    ps.setString(14, orderBodyDO.getRemark());
                    ps.setBoolean(15, orderBodyDO.getDeleted());
                    ps.setTimestamp(16, Timestamp.valueOf(orderBodyDO.getCreateTime()));
                    ps.setTimestamp(17, Timestamp.valueOf(orderBodyDO.getUpdateTime()));
                    ps.setString(18, orderBodyDO.getCreator());
                    ps.setString(19, orderBodyDO.getUpdater());
                    ps.setLong(20, cadViewFileId);
                    ps.setInt(21, orderBodyDO.getMultiSort());
                }

                @Override
                public int getBatchSize() {
                    return bodys.size();
                }
            });
        }
    }

    /**
     * 插入加工组
     *
     * @param groupList
     */
    private void saveGroup(List<List<OrderGroupDO>> groupList) {
        for (List<OrderGroupDO> groups : groupList) {
            String sql = "INSERT INTO order_group (id, organ_id, order_id, body_id, group_type_id, group_type_name, name, width, height, depth, multi_num, plate_num, unregular_num, filename, remark, deleted,create_time,update_time,creator,updater) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?,?,?)";
            jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int index) throws SQLException {
                    OrderGroupDO orderGroupDO = groups.get(index);
                    ps.setLong(1, orderGroupDO.getId());
                    ps.setLong(2, orderGroupDO.getOrganId());
                    ps.setLong(3, orderGroupDO.getOrderId());
                    ps.setLong(4, orderGroupDO.getBodyId());
                    ps.setLong(5, orderGroupDO.getGroupTypeId());
                    ps.setString(6, orderGroupDO.getGroupTypeName());
                    ps.setString(7, orderGroupDO.getName());
                    ps.setDouble(8, orderGroupDO.getWidth());
                    ps.setDouble(9, orderGroupDO.getHeight());
                    ps.setDouble(10, orderGroupDO.getDepth());
                    ps.setInt(11, orderGroupDO.getMultiNum());
                    ps.setInt(12, orderGroupDO.getPlateNum());
                    ps.setInt(13, orderGroupDO.getUnregularNum());
                    ps.setString(14, orderGroupDO.getFilename());
                    ps.setString(15, orderGroupDO.getRemark());
                    ps.setBoolean(16, orderGroupDO.getDeleted());
                    ps.setTimestamp(17, Timestamp.valueOf(orderGroupDO.getCreateTime()));
                    ps.setTimestamp(18, Timestamp.valueOf(orderGroupDO.getUpdateTime()));
                    ps.setString(19, orderGroupDO.getCreator());
                    ps.setString(20, orderGroupDO.getUpdater());
                }

                @Override
                public int getBatchSize() {
                    return groups.size();
                }
            });
        }
    }

    /**
     * 每batchSize条拆分dataList
     *
     * @param dataList
     */
    private <T> List<List<T>> batchProcess(List<T> dataList) {
        List<List<T>> resultList = new ArrayList<>();
        if (CollUtil.isEmpty(dataList)) {
            return resultList;
        }

        int total = dataList.size();
        for (int i = 0; i < total; i += BATCH_THRESHOLD_NUMBER) {
            int end = Math.min(i + BATCH_THRESHOLD_NUMBER, total);
            List<T> subList = dataList.subList(i, end);

            if (!subList.isEmpty()) {
                resultList.add(subList);
            }
        }
        return resultList;
    }

    /**
     * 更新订单的板件总数
     */
    private void updateOrderPlateNum() {
        int version = orderDO.getVersion();
        Long orderId = orderDO.getId();
        int updated = orderMapper.update(new LambdaUpdateWrapper<OrderDO>()
                .eq(OrderDO::getId, orderId)
                .eq(OrderDO::getOrganId, organId)
                .eq(OrderDO::getVersion, version)
                .set(OrderDO::getPlateNum, currentPlateNum)
                .set(OrderDO::getArea, currentPlateArea)
                .set(OrderDO::getVersion, version + 1));
        if (updated == 0) {
            throw new ServiceException(ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORDER_UPDATE_CONCURRENCY_ERROR, orderId);
        }
    }

    /**
     * 新增order_goods/更新已有order_goods的板件总数
     */
    private void insertOrUpdatePlateNumGoods() {
        if (MapUtil.isNotEmpty(materialMap)) {
            // 分类新增和更新的goods
            List<GoodsDO> insertGoods = new ArrayList<>();
            List<GoodsDO> updateGoods = new ArrayList<>();
            materialMap.forEach((k, v) -> {
                if (v.isExist()) {
                    updateGoods.add(v);
                } else {
                    insertGoods.add(v);
                }
            });

            // 批量插入order_goods
            orderInfoBatchInsertWithinThreshold(goodsMapper, insertGoods, true);

            // 批量更新order_goods的plateNum
            for (GoodsDO updateDo : updateGoods) {
                int updated = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                        .eq(GoodsDO::getId, updateDo.getId())
                        .eq(GoodsDO::getOrganId, organId)
                        .eq(GoodsDO::getVersion, updateDo.getVersion())
                        .set(GoodsDO::getPlateNum, updateDo.getPlateNum())
                        .set(GoodsDO::getVersion, updateDo.getVersion() + 1));
                if (updated == 0) {
                    throw new ServiceException(ErrorCodeConstants.WEBCAD_ORDER_IMPORT_GOODS_UPDATE_CONCURRENCY_ERROR, updateDo.getId());
                }
            }
        }
    }

    private void setSqlDefaultBigDecimal(PreparedStatement ps, int index, BigDecimal value) throws SQLException {
        if (value == null) {
            ps.setBigDecimal(index, BigDecimal.ZERO);
        } else {
            ps.setBigDecimal(index, value);
        }
    }

    /**
     * 清空所有列表
     */
    private void clearAll() {
        roomBodyMap = null;
        bodyMap = null;
        orderGroupMap = null;
        materialMap = null;
        processGroupMap = null;
        existRoomIdMap = null;
        orderPartMap = null;
        componentGroupMap = null;

        plateDOS = null;
        orderItemDOS = null;
        orderPartsDOS = null;
        goodsDOS = null;
        orderComponentDOS = null;
        sealEdgeConfigList = null;
    }
}
