package com.cf.imes.module.executor.service.plan;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch.core.UpdateByQueryRequest;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.cf.imes.framework.common.enums.*;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.es.core.service.ESDocumentService;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.mybatis.core.query.QueryWrapperX;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.executor.controller.admin.goods.vo.GoodsRespVO;
import com.cf.imes.module.executor.controller.admin.order.vo.order.OrderPlanPlateNumAndAreaRespVO;
import com.cf.imes.module.executor.controller.admin.order.vo.order.OrderRespVO;
import com.cf.imes.module.executor.controller.admin.plan.bo.OrderRoomBodyList;
import com.cf.imes.module.executor.controller.admin.plan.saveOptimize.PlatePlaceInfo;
import com.cf.imes.module.executor.controller.admin.plan.saveOptimize.ProcessSchemeModelDO;
import com.cf.imes.module.executor.controller.admin.plan.saveOptimize.RemainBoardInfo;
import com.cf.imes.module.executor.controller.admin.plan.vo.*;
import com.cf.imes.module.executor.controller.admin.plate.vo.OrderPlateIds;
import com.cf.imes.module.executor.dal.dataobject.goods.GoodsDO;
import com.cf.imes.module.executor.dal.dataobject.goods.OptimizeBoardModelDO;
import com.cf.imes.module.executor.dal.dataobject.goods.PlanActualGoodsModelDO;
import com.cf.imes.module.executor.dal.dataobject.order.OrderDO;
import com.cf.imes.module.executor.dal.dataobject.orderBody.OrderBodyDO;
import com.cf.imes.module.executor.dal.dataobject.orderItem.OrderItemDO;
import com.cf.imes.module.executor.dal.dataobject.plan.PlanDO;
import com.cf.imes.module.executor.dal.dataobject.planitem.PlanItemDO;
import com.cf.imes.module.executor.dal.dataobject.plate.PlateDO;
import com.cf.imes.module.executor.dal.mysql.goods.GoodsMapper;
import com.cf.imes.module.executor.dal.mysql.order.OrderMapper;
import com.cf.imes.module.executor.dal.mysql.orderBody.OrderBodyMapper;
import com.cf.imes.module.executor.dal.mysql.orderItem.OrderItemMapper;
import com.cf.imes.module.executor.dal.mysql.plan.PlanMapper;
import com.cf.imes.module.executor.dal.mysql.planItem.PlanItemMapper;
import com.cf.imes.module.executor.dal.mysql.plate.PlateMapper;
import com.cf.imes.module.executor.dal.mysql.remainplaten.RemainPlateMapper;
import com.cf.imes.module.executor.enums.ErrorCodeConstants;
import com.cf.imes.module.executor.enums.OrderPlanQueryTypeEnum;
import com.cf.imes.module.executor.enums.OrderStatusEnum;
import com.cf.imes.module.executor.util.elasticsearch.EsUtils;
import com.cf.imes.module.system.api.systemconfig.SystemConfigApi;
import com.cf.imes.module.system.api.systemconfig.dto.SystemConfigRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;

import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.DATA_DATA_ERROR;
import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.ES_DATA_ERROR;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.framework.common.util.json.JsonUtils.parseArray;
import static com.cf.imes.framework.common.util.json.JsonUtils.toJsonString;
import static com.cf.imes.framework.common.util.string.SearchUtil.insertSeparator;
import static com.cf.imes.framework.security.core.util.SecurityFrameworkUtils.getUserOrganId;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.*;
import static com.cf.imes.module.executor.enums.EsIndexEnum.*;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;

/**
 * 生产单开料排单 Service 实现类
 *
 * @author 晨丰科技
 */
@Service
@Validated
@Slf4j
public class PlanServiceImpl implements PlanService {

    @Resource
    private PlanMapper planMapper;

    @Resource
    private GoodsMapper goodsMapper;

    @Resource
    private PlanItemMapper planItemMapper;

    @Resource
    private OrderItemMapper orderItemMapper;

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private PlateMapper plateMapper;

    @Resource
    private SystemConfigApi systemConfigApi;

    @Resource
    private OrderBodyMapper orderBodyMapper;

    @Resource
    private RemainPlateMapper remainPlateMapper;

    @Resource
    private IdentifierGenerator identifierGenerator;

    @Resource
    private ElasticsearchClient elasticsearchClient;

    @Resource
    private ESDocumentService esDocumentService;

    @Resource
    private EsUtils esUtils;


    // 重复使用的常量
    private static final String FIELD_OP_IS_SPECIAL_SHAPED = "op.is_special_shaped";
    private static final String FIELD_PLANID = "planId";
    private static final String FIELD_ORGAN_ID = "o.organ_id";
    private static final String FIELD_DELETED = "o.deleted";
    private static final String FIELD_STATUS = "o.status";
    private static final String FIELD_PLAN_ID = "op.plan_id";
    private static final String FIELD_CUSTOMER = "o.customer";
    private static final String FIELD_CUSTOM_ORDER_NO = "o.custom_order_no";
    private static final String FIELD_ADDRESS = "o.address";
    private static final String FIELD_ORDER_DATE = "o.order_date";
    private static final String FIELD_OP_ID = "op.id";
    private static final String FIELD_OP_DELETED = "op.deleted";
    private static final String ES_FIELD_NEWVALUE = "new_value";
    private static final String ORDER_PLAN_PROCESS_SCHEME_STATUS_UPDATE_ERROR_MSG = "排单加工方案状态更新异常：";

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createPlan(PlanSaveReqVO createReqVO) {

        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        assert loginUser != null;

        SystemConfigRespDTO systemConfigRespDTO = systemConfigApi.getDefaultProcessConfig().getCheckedData();

        Long processId = systemConfigRespDTO.getId();

        List<PlanDO> planDOS = new ArrayList<>();

        SavePlanPlateList savePlanPlateList = Optional.ofNullable(createReqVO.getSavePlanPlateList()).orElseThrow(()-> new ServiceException(PLAN_PLATE_DATA_ERROR));


        if (savePlanPlateList.getMixConfig() != null && Boolean.TRUE.equals(savePlanPlateList.getMixConfig().getIsMixed())) {

            // 混单时走的逻辑
            Integer mixedType = Optional.ofNullable(savePlanPlateList.getMixConfig().getMixedType()).orElse(MixConfigTypeEnum.SAMEMATERIALANDTHICKNESS.getType());

            List<SavePlanPlateList.PlateItemList> plateItemList = Optional.ofNullable(savePlanPlateList.getPlateItemList()).orElseThrow(()-> new ServiceException(PLAN_PLATE_DATA_ERROR));

            List<SavePlanPlateList.GoodsItemList> goodsItemList = Optional.ofNullable(savePlanPlateList.getGoodsItemList()).orElseThrow(()-> new ServiceException(PLAN_PLATE_DATA_ERROR));

            List<PlanActualGoodsList> planActualGoodsLists = Optional.ofNullable(createReqVO.getPlanActualGoodsListList()).orElse(new ArrayList<>());

            // 混单时判断是否进行板材对应并创建排单
            mixConfigPlateCorresponding(planActualGoodsLists, plateItemList, goodsItemList, planDOS, processId, loginUser, mixedType);

        } else {

            // 非混单
            List<SavePlanPlateList.PlateItemList> plateItemList = Optional.ofNullable(savePlanPlateList.getPlateItemList()).orElseThrow(()-> new ServiceException(PLAN_PLATE_DATA_ERROR));

            List<SavePlanPlateList.GoodsItemList> goodsItemList = Optional.ofNullable(savePlanPlateList.getGoodsItemList()).orElseThrow(()-> new ServiceException(PLAN_PLATE_DATA_ERROR));
            // 查找材质和厚度和颜色相同的大板
            Map<String, List<Long>> grouped = groupByMaterialAndThicknessAndColorAndBrand(goodsItemList);

            createPlanAndPlateDataList(grouped, plateItemList, planDOS, processId, loginUser, PlanTypeEnum.ORDERPLAN.getType());
        }

        // 增加排单
        planMapper.insertBatch(planDOS);

        // 排单对应的加工方案组的配置信息
        List<ProcessSchemeModelDO> processSchemeModelDOS;

        try {
            processSchemeModelDOS = parseArray(systemConfigRespDTO.getSetting(), ProcessSchemeModelDO.class);

        }catch (Exception e){
            log.error(e.getMessage());
            throw exception(SYSTEM_PROCESS_SCHEME_CONFIG_FIELD_ERROR);
        }

        planDOS.forEach(f-> processSchemeModelDOS.forEach(p->{
            p.setPlanId(f.getId());
            p.setProcessId(processId);
        }));

        esUtils.saveEsDocument(PLAN_PROCESS_SCHEME_CONFIG.getIndex(),processSchemeModelDOS);
    }




    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePlan(PlanSaveReqVO updateReqVO) {

        Long planId = updateReqVO.getId();

        if(planId != null) {

            PlanDO planDO = validatePlan(planId);

            if(updateReqVO.getProcessId() != null){

                if(planDO.getStatus().equals(PlanStatusEnum.OPENING.getStatus()) || planDO.getStatus().equals(PlanStatusEnum.OPENED.getStatus())){
                    throw exception(THIS_PLAN_PLATE_IS_CUT);
                }

                planDO.setProcessId(updateReqVO.getProcessId());

                planMapper.updateById(planDO);

            }else {

                planDO.setIsScheduled(updateReqVO.getIsScheduled());
                planMapper.updateById(planDO);
            }

            return;
        }


        // 删除小板
        List<OrderPlateIds> deleteOrderPlateIds = Optional.ofNullable(updateReqVO.getDeleteOrderPlateIds()).orElse(new ArrayList<>());

        if(CollUtil.isNotEmpty(deleteOrderPlateIds)){

            PlanServiceImpl p = (PlanServiceImpl) AopContext.currentProxy();
            p.deletePlanPlate(deleteOrderPlateIds);

            return;
        }


        // 添加小板
        List<OrderPlateIds> insertOrderPlateIds = Optional.ofNullable(updateReqVO.getInsertOrderPlateIds()).orElse(new ArrayList<>());

        PlanActualGoodsModelDO planActualGoodsModelDO = updateReqVO.getPlanActualGoodsModelDO();

        if(CollUtil.isNotEmpty(insertOrderPlateIds)){

            PlanServiceImpl p = (PlanServiceImpl) AopContext.currentProxy();
            p.insertPlanPlate(insertOrderPlateIds,planActualGoodsModelDO);

        }

    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePlan(List<Long> planIds) {
        // 校验存在
        List<PlanDO> planDOS = planMapper.selectBatchIds(planIds);

        AssertUtils.notEmpty(planDOS,PLAN_NOT_EXISTS);

        // 获取排单下的订单号集合
        planDOS.forEach(f->{
            if ( f.getStatus().equals(PlanStatusEnum.OPENING.getStatus()) || f.getStatus().equals(PlanStatusEnum.OPENED.getStatus()) ) {
                throw exception(PLAN_NOT_ALLOW_DELETE,f.getId());
            }
        });

        // 删除排单信息，更新排单对应的余料板的排单ID为0
        planMapper.deleteBatchIds(planIds);

        remainPlateMapper.deletePlanRemainPlate(planIds, getUserOrganId());

        // 重置板件排单和优化属性 & 计算orders和order_goods的数量和面积
        Set<Long> orderIds = computePlanOrderAndGoodsPlate(planDOS);

        // 查询当前排单是否有自增板，如果有，删除
        List<PlateDO> plateDOS = plateMapper.selectSelfincreasePlateIdList(orderIds, planIds, getUserOrganId());

        if (!plateDOS.isEmpty()) {
            List<Long> plateIdList = plateDOS.stream().map(PlateDO::getId).toList();

            plateMapper.deleteBatchIds(plateIdList);

            orderItemMapper.delete(new LambdaQueryWrapperX<OrderItemDO>()
                    .eq(OrderItemDO::getOrganId, getUserOrganId())
                    .inIfPresent(OrderItemDO::getOrderId, orderIds)
                    .in(OrderItemDO::getPlateId, plateIdList));

        }

        // 排单对应的优化数据删除
        esUtils.deleteEsDocument(FIELD_PLANID, planIds, ORDER_OPTIMIZE_PLATE_MODEL.getIndex());


        // 删除排单对应的加工方案的优化数据
        esUtils.deleteEsDocument(FIELD_PLANID, planIds, PLAN_PROCESS_SCHEME_OPTIMIZE_MODEL.getIndex());


        // 删除排单对应的加工方案组的配置信息
        esUtils.deleteEsDocument(FIELD_PLANID, planIds, PLAN_PROCESS_SCHEME_CONFIG.getIndex());

        // 去除生产单板材造型对应的排单ID
//        removePlateModelPlanId(orderIds,planIds,new ArrayList<>());

    }

    /**
     * 重置板件排单和优化属性 & 计算orders和order_goods的数量和面积
     *
     * @param planDOS
     */
    private Set<Long> computePlanOrderAndGoodsPlate(List<PlanDO> planDOS) {
        // 所有涉及移除排单的订单号列表
        Set<Long> orderIds = new HashSet<>();
        for (PlanDO planDO : planDOS) {
            // 重置排单下的板件 排单和优化属性
            plateMapper.update(new LambdaUpdateWrapper<PlateDO>()
                    .eq(PlateDO::getPlanId, planDO.getId())
                    .eq(PlateDO::getOrganId, getUserOrganId())
                    .set(PlateDO::getPlanId, 0L)
                    .set(PlateDO::getIsOptimized, false));

            String orderNos = Optional.ofNullable(planDO.getOrderNos()).orElse("[]");

            // 排单下的订单号列表
            Set<Long> planOrderIds = JSON.parseArray(orderNos).toJavaList(Long.class).stream().collect(Collectors.toSet());
            orderIds.addAll(planOrderIds);

            if (CollUtil.isNotEmpty(planOrderIds)) {
                // 查询排单下所有订单材质order_goods
                List<GoodsDO> goodsDO = goodsMapper.selectList(new LambdaQueryWrapper<GoodsDO>()
                        .in(GoodsDO::getOrderId, planOrderIds)
                        .eq(GoodsDO::getOrganId, getUserOrganId())
                        .eq(GoodsDO::getDeleted, false));

                for (GoodsDO planGoods : goodsDO) {
                    Long orderGoodsId = planGoods.getId();
                    int version = planGoods.getVersion();
                    // 统计每个order_goods下的未排单面积，更新到order_goods中
                    OrderPlanPlateNumAndAreaRespVO goodsPlateNumAndAreaPlanInfo = goodsMapper.selectPlannedPlateNumAndArea(orderGoodsId);
                    int update = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                            .eq(GoodsDO::getId, orderGoodsId)
                            .eq(GoodsDO::getVersion, version)
                            .set(GoodsDO::getPlannedPlateArea, goodsPlateNumAndAreaPlanInfo.getArea())
                            .set(GoodsDO::getPlannedPlateNum, goodsPlateNumAndAreaPlanInfo.getNum())
                            .set(GoodsDO::getVersion, version + 1));
                    if (update == 0) {
                        throw ServiceExceptionUtil.exception(ErrorCodeConstants.GOODS_PLATE_UPDATE_CONCURRENCY_ERROR, orderGoodsId);
                    }
                }
            }
        }

        // 统计更新orders的小板数量和面积
        computeOrderInfo(orderIds);
        return orderIds;
    }




    @Override
    public PlanRespVO getPlan(Long id) {

        PlanDO planDO = planMapper.selectById(id);

        AssertUtils.notEmpty(planDO,PLAN_NOT_EXISTS);

        return BeanUtils.toBean(planDO, PlanRespVO.class);


    }



    @Override
    public PageResult<PlanRespVO> getPlanPage(PlanPageReqVO pageReqVO) {

        PageDTO<PlanDO> page = new PageDTO<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());

        IPage<PlanDO> planDOPageResult = new PageDTO<>();

        // 是否进行翻页
        if(pageReqVO.getIsUp() != null) {

            planPageTurning(pageReqVO,planDOPageResult);

        }else {

            QueryWrapperX<PlanDO> queryWrapperX = new QueryWrapperX<>();
            queryWrapperX
                    .eq("op.organ_id",getUserOrganId())
                    .eq(FIELD_OP_DELETED,false)
                    .eqIfPresent("op.is_scheduled",pageReqVO.getIsScheduled())
                    .eqIfPresent(FIELD_OP_ID, pageReqVO.getId())
                    .eqIfPresent("op.sort", pageReqVO.getSort())
                    .eqIfPresent("op.type", pageReqVO.getType())
                    .inIfPresent("op.status", pageReqVO.getStatus())
                    .betweenIfPresent("op.plan_time", pageReqVO.getPlanTime())
                    .likeIfPresent("op.order_nos", pageReqVO.getOrderNos())
                    .likeIfPresent(FIELD_CUSTOMER, pageReqVO.getCustomer());


            planDOPageResult = planMapper.selectPlanPageList(page,queryWrapperX);

            if (CollUtil.isEmpty(planDOPageResult.getRecords())) {
                return new PageResult<>();
            }

        }


        List<Long> processIds = planDOPageResult.getRecords().stream().map(PlanDO::getProcessId).distinct().toList();

        List<Long> planIds = planDOPageResult.getRecords().stream().map(PlanDO::getId).toList();


        List<SystemConfigRespDTO> configDOS = systemConfigApi.getProcessByIds(processIds).getData();


        // 查询排单对应的大板板材信息以及 统计小板数量和面积
        List<PlateInfoVO> plateInfoVOS = planItemMapper.selectOrderPlanPlateList(planIds, getUserOrganId());


        // 根据排单ID查询ES中的优化数据
        List<OptimizeBoardModelDO> optimizeBoardModelDOS = esUtils.getEsDocument(FIELD_PLANID, planIds, 500, ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), OptimizeBoardModelDO.class);


        // 获取排单对应的生产单的信息
        List<PlanOrderRespVO> planOrderRespVOS = orderMapper.selectPlanOrderList(planIds, getUserOrganId());


        List<PlanRespVO> planRespVOPageResult = BeanUtils.toBean(planDOPageResult.getRecords(), PlanRespVO.class);


        planRespVOPageResult.forEach(e -> {
            // 方案信息
            e.setProcessName(getProcessName(e.getProcessId(), configDOS));
            // 大板信息列表
            e.setPlateInfoList(getPlateInfoList(e.getId(), plateInfoVOS));
            // 小板的总数量
            e.setPlateNum(getPlateNum(e.getId(), plateInfoVOS));
            // 大板总数量
            e.setGoodsCount(getOptimizeGoodsCount(e.getId(), optimizeBoardModelDOS));
            // 大板开料数
            e.setCutGoodsCount(getCutGoodsCount(e.getId(), optimizeBoardModelDOS));
            // 小板优化数量
            e.setOptimizePlateCount(getOptimizePlateCount(e.getId(), optimizeBoardModelDOS));
            // 未优化小板数量
            e.setUnOptimizePlateCount(e.getPlateNum() - e.getOptimizePlateCount());
            // 生产单信息
            e.setOrderList(getOrderList(e.getId(), planOrderRespVOS));
        });


        return new PageResult<>(planRespVOPageResult, planDOPageResult.getTotal());
    }


    @Override
    public List<PlanRespVO> getPlanList() {

        List<Integer> status = Arrays.asList(PlanStatusEnum.NOCUTTING.getStatus(),PlanStatusEnum.OPENING.getStatus());

        List<PlanDO> planDOList = planMapper.selectPlanSortList(status, getUserOrganId());

        if (CollUtil.isEmpty(planDOList)) {
            return new ArrayList<>();
        }


        List<Long> processIds = planDOList.stream().map(PlanDO::getProcessId).distinct().toList();

        List<Long> planIds = planDOList.stream().map(PlanDO::getId).toList();


        List<SystemConfigRespDTO> configDOS = systemConfigApi.getProcessByIds(processIds).getCheckedData();


        // 查询排单对应的大板板材信息
        List<PlateInfoVO> plateInfoVOS = goodsMapper.selectOrderGoodsPlateList(planIds, getUserOrganId());


        if(CollUtil.isNotEmpty(plateInfoVOS)) {
            // 查询排单对应的小板数量和小板面积
            plateInfoVOS.forEach(m -> {

                m.setCount((int) m.getPlatePages().stream().map(PlatePage::getPlateId).count());

                m.setArea(m.getPlatePages().stream().map(PlatePage::getArea).reduce(BigDecimal.ZERO, BigDecimal::add));
                m.setPlatePages(null);

            });
        }

        // 根据排单ID查询ES中的优化数据
        List<OptimizeBoardModelDO> optimizeBoardModelDOS = esUtils.getEsDocument(FIELD_PLANID, planIds, planIds.size(), ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), OptimizeBoardModelDO.class);


        // 获取排单对应的生产单的信息
        List<PlanOrderRespVO> planOrderRespVOS = orderMapper.selectPlanOrderList(planIds, getUserOrganId());


        List<PlanRespVO> planRespVOList = BeanUtils.toBean(planDOList, PlanRespVO.class);


        planRespVOList.forEach(e -> {
            // 机台信息
            e.setProcessName(getProcessName(e.getProcessId(), configDOS));
            // 板材信息列表
            e.setPlateInfoList(getPlateInfoList(e.getId(), plateInfoVOS));
            // 小板总数量
            e.setPlateNum(getPlateNum(e.getId(), plateInfoVOS));
            // 大板总数量
            e.setGoodsCount(getOptimizeGoodsCount(e.getId(), optimizeBoardModelDOS));
            // 大板开料数
            e.setCutGoodsCount(getCutGoodsCount(e.getId(), optimizeBoardModelDOS));
            // 小板优化数量
            e.setOptimizePlateCount(getOptimizePlateCount(e.getId(), optimizeBoardModelDOS));
            // 未优化小板数量
            e.setUnOptimizePlateCount(e.getPlateNum() - e.getOptimizePlateCount());
            // 生产单信息
            e.setOrderList(getOrderList(e.getId(), planOrderRespVOS));
        });


        return planRespVOList;


    }


    @Override
    public PageResult<OrderRespVOCopy> getOrderPage(OrderPageReqVOCopy pageReqVO) {

        List<Integer> statusList = new ArrayList<>();

        statusList.add(OrderStatusEnum.NEW_ORDER.getStatus());
        statusList.add(OrderStatusEnum.IN_PRODUCTION.getStatus());
        statusList.add(OrderStatusEnum.SORTED.getStatus());

        List<String> goodsId = Optional.ofNullable(pageReqVO.getGoodsIds()).orElse(new ArrayList<>());

        List<Long> ids = new ArrayList<>();
        // 查询类型：订单分组
        int type = OrderPlanQueryTypeEnum.ORDER_GROUP.getType();
        if(CollUtil.isNotEmpty(goodsId)) {

            List<Long> orderIdList = Optional.ofNullable(pageReqVO.getOrderIds()).orElse(new ArrayList<>());

            List<GoodsDO> goodsDOS = goodsMapper.selectListByGoodsId(orderIdList,goodsId, getUserOrganId());
            ids.addAll(goodsDOS.stream().map(GoodsDO::getId).toList());
            type = OrderPlanQueryTypeEnum.GOODS_GROUP.getType();
        }

        QueryWrapperX<OrderDO> queryWrapperX = new QueryWrapperX<>();
        queryWrapperX
                .eq(FIELD_ORGAN_ID,getUserOrganId())
                .eq(FIELD_DELETED,false)
                .in(FIELD_STATUS,statusList)

                .inIfPresent("og.id",ids)
                .inIfPresent("o.id",pageReqVO.getOrderIds())
                .likeIfPresent("o.id", pageReqVO.getOrderId() != null ? pageReqVO.getOrderId() : null)
                .likeIfPresent(FIELD_CUSTOMER, pageReqVO.getConsignee())
                .likeIfPresent(FIELD_CUSTOM_ORDER_NO, pageReqVO.getDefaultId())
                .likeIfPresent(FIELD_ADDRESS, pageReqVO.getConsigneeAddress())
                .betweenIfPresent(FIELD_ORDER_DATE, pageReqVO.getCreateTime());
        PageDTO<OrderRespVOCopy> page = new PageDTO<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        IPage<OrderRespVOCopy> orderList = planItemMapper.selectOrderList(page, queryWrapperX, type);
        return new PageResult<>(orderList.getRecords(), orderList.getTotal());

    }





    @Override
    public PageResult<OrderGoodsResp> getOrderGoodsPage(OrderPageReqVOCopy pageReqVO) {

        PageDTO<OrderGoodsResp> page = new PageDTO<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());

        List<Integer> statusList = new ArrayList<>();
        statusList.add(OrderStatusEnum.NEW_ORDER.getStatus());
        statusList.add(OrderStatusEnum.IN_PRODUCTION.getStatus());
        statusList.add(OrderStatusEnum.SORTED.getStatus());

        QueryWrapperX<PlanItemDO> queryWrapperX = new QueryWrapperX<>();
        IPage<OrderGoodsResp> orderGoodsResps = new PageDTO<>();

        Integer type = 0;
        if (CollUtil.isNotEmpty(pageReqVO.getRoomId()) || CollUtil.isNotEmpty(pageReqVO.getBodyId())) {
            // 增加房间id、柜体id条件
            queryWrapperX
                    .inIfPresent("oi.order_id", pageReqVO.getOrderIds())
                    .inIfPresent("oi.room_id", pageReqVO.getRoomId())
                    .inIfPresent("oi.body_id", pageReqVO.getBodyId());
            orderGoodsResps = planItemMapper.selectNoPlanGoodsByRoomAndBodyIds(page,queryWrapperX,getUserOrganId());
            type = OrderPlanQueryTypeEnum.GOODS_ROOMBODY_QUERY.getType();
        }else {
            queryWrapperX
                    .eq(FIELD_DELETED, false)
                    .in(FIELD_STATUS, statusList)
                    .inIfPresent("o.id", pageReqVO.getOrderIds())
                    .likeIfPresent("o.id", pageReqVO.getOrderId())
                    .likeIfPresent(FIELD_CUSTOMER, pageReqVO.getConsignee())
                    .likeIfPresent(FIELD_CUSTOM_ORDER_NO, pageReqVO.getDefaultId())
                    .likeIfPresent(FIELD_ADDRESS, pageReqVO.getConsigneeAddress())
                    .betweenIfPresent(FIELD_ORDER_DATE, pageReqVO.getCreateTime());

            orderGoodsResps = planItemMapper.selectNoPlanGoodsList(page,queryWrapperX,getUserOrganId());

        }


        List<OrderGoodsResp> records = orderGoodsResps.getRecords();
        if (CollUtil.isEmpty(records)) {
            return new PageResult<>();
        }

        List<String> goodsName = records.stream().map(OrderGoodsResp::getGoodsName).distinct().toList();

        List<String> material = records.stream().map(OrderGoodsResp::getMaterial).distinct().toList();

        List<String> color = records.stream().map(OrderGoodsResp::getColor).distinct().toList();

        List<BigDecimal> thickness = records.stream().map(OrderGoodsResp::getThickness).distinct().toList();

        List<String> brand = records.stream().map(OrderGoodsResp::getBrand).distinct().toList();

        List<Boolean> texture = records.stream().map(OrderGoodsResp::getTexture).distinct().toList();


        queryWrapperX
                .inIfPresent("og.goods_name",goodsName)
                .inIfPresent("og.material",material)
                .inIfPresent("og.color",color)
                .inIfPresent("og.thickness",thickness)
                .inIfPresent("og.brand",brand)
                .inIfPresent("og.texture", texture);

        List<OrderGoodsResp> orderGoodsIdList = planItemMapper.selectNoOrderGoodsIdList(queryWrapperX, getUserOrganId(), type);

        mergeOrderGoodsLists(records,orderGoodsIdList);

        return new PageResult<>(orderGoodsResps.getRecords(), orderGoodsResps.getTotal());

    }




    @Override
    public PageResult<PlateResList> getPlateByPlanId(GetPlateByPlanIdVO vo) {
        if(vo.getPlanIds().isEmpty()){
            return null;
        }

        List<Long> planIds = vo.getPlanIds();

        List<PlanDO> planDOS = planMapper.selectBatchIds(planIds);

        if(planDOS.size() != planIds.size()){
            planDOS.forEach(f->{
                if(!planIds.contains(f.getId())){
                    throw exception(PLAN_NOT_DATA_EXISTS,f.getId());
                }
            });
        }

        QueryWrapperX<PlanItemDO> queryWrapperX = new QueryWrapperX<>();

        String filterTypes = "";
        if (Objects.nonNull(vo.getHoleThrough()) && vo.getHoleThrough()) {
            filterTypes += PlanFilterTypeEnum.DIGGINGTHROUGHTHESHAPE.getType().toString();
        }
        if (Objects.nonNull(vo.getBurrow()) && vo.getBurrow()) {
            filterTypes += PlanFilterTypeEnum.THEREAREPERFORATEDHOLES.getType().toString();
        }
        if (Objects.nonNull(vo.getTwoDimensionalToolPath()) && vo.getTwoDimensionalToolPath()) {
            filterTypes += PlanFilterTypeEnum.TWODIMENSIONALCUTTINGPATH.getType().toString();
        }

        queryWrapperX.eq("op.organ_id",getUserOrganId())
                .in(FIELD_PLAN_ID, vo.getPlanIds())
                .eqIfPresent("op.is_door", vo.getIsDoor())
                .eqIfPresent(FIELD_OP_IS_SPECIAL_SHAPED, vo.getRectangle() != null ? false: null)
                .eqIfPresent(FIELD_OP_IS_SPECIAL_SHAPED, vo.getSpecialShaped())
                .eqIfPresent("op.is_sculpt", vo.getSculpt())
                .betweenIfPresent("op.width", new BigDecimal[]{vo.getWidthMinRang(), vo.getWidthMaxRang()})
                .betweenIfPresent("op.height", new BigDecimal[]{vo.getLongMinRang(), vo.getLongMaxRang()})
                .likeIfPresent("op.filter_type", insertSeparator(filterTypes));

        Integer pageNo = (vo.getPageNo() - 1) * vo.getPageSize();

        List<PlateResList> pageResult = planItemMapper.selectPlateListByPlateIds(queryWrapperX,pageNo,vo.getPageSize());

        if (CollUtil.isEmpty(pageResult)) {
            return new PageResult<>();
        }

        Long sum = planItemMapper.selectPlateCountByPlateIds(queryWrapperX);

        return new PageResult<>(pageResult, sum);

    }





    @Override
    public PageResult<OrderRespVO> getOrderByPlanId(PlanOrderPageReqVO pageReqVO) {

        PlanDO planDO = validatePlan(pageReqVO.getPlanId());

        List<Long> orderIds = JSON.parseArray(planDO.getOrderNos()).toJavaList(Long.class).stream().distinct().toList();


        List<OrderDO> orderDOList = orderMapper.selectBatchIds(orderIds);

        return new PageResult<>(BeanUtils.toBean(orderDOList, OrderRespVO.class), (long) orderDOList.size());

    }




    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setSort(List<SortVO> sortVos) {

        List<Long> planIds = sortVos.stream().map(SortVO::getPlanId).toList();

        List<Long> sorts = new ArrayList<>();

        for (Long planId : planIds) {

            PlanDO planDO = planMapper.selectById(planId);

            if(planDO == null){
                throw exception(PLAN_NOT_DATA_EXISTS,planId);
            }

            sorts.add(planDO.getSort());
        }


        if(sortVos.size() == 1){

            SortVO sortVO = sortVos.get(0);

            if(sortVO.getIsOnePlan() != null || sortVO.getIsLastPln() != null){

                PlanDO planDO = planMapper.selectById(sortVO.getPlanId());

                Long sort = planDO.getSort();

                PlanDO plan = new PlanDO();

                if(Boolean.TRUE.equals(sortVO.getIsOnePlan())){
                    plan = planMapper.selectSortOnePlan(sort, getUserOrganId());

                }else if(Boolean.TRUE.equals(sortVO.getIsLastPln())){
                    plan = planMapper.selectLastPlan(sort, getUserOrganId());
                }


                AssertUtils.notEmpty(plan,PLAN_SORT_DATA_ERROR);

                planDO.setSort(plan.getSort());

                plan.setSort(sort);

                planMapper.updateById(planDO);
                planMapper.updateById(plan);

                return;

            }


            planMapper.update(new LambdaUpdateWrapper<PlanDO>()
                    .eq(PlanDO::getOrganId, getUserOrganId())
                    .eq(PlanDO::getDeleted,false)
                    .eq(PlanDO::getId,sortVO.getPlanId())
                    .set(PlanDO::getIsTop,sortVO.getIsTopUp()));

            return;

        }


        ArrayList<PlanDO> planDOS = new ArrayList<>();

        sortVos.forEach(e -> {

            planDOS.add(PlanDO.builder()
                    .id(e.getPlanId())
                    .sort(sorts.stream().filter(f -> !f.equals(e.getSort())).findFirst().orElse(e.getSort()))
                    .build());

        });

        planMapper.updateBatch(planDOS);

    }



    @Override
    public List<PlateResList> getPlateList(List<Long> planIds) {

        for (Long planId : planIds) {
            validatePlan(planId);
        }

        List<PlateResList> plateResLists = goodsMapper.selectPlateListByPlanId(planIds, getUserOrganId());

        Set<Long> bodyIds = plateResLists.stream().map(PlateResList::getBodyId).collect(Collectors.toSet());


        if(plateResLists.isEmpty() || bodyIds.isEmpty()){
            return new ArrayList<>();
        }

        List<OrderBodyDO> orderBodyDOS = orderBodyMapper.selectBatchIds(bodyIds);

        if(orderBodyDOS.isEmpty()){
            return plateResLists;
        }

        plateResLists.forEach(f->{
            f.setBodyName(orderBodyDOS.stream().filter(e -> Objects.equals(e.getId(), f.getBodyId())).map(OrderBodyDO::getName).findAny().orElse(null));
            f.setRoomName(orderBodyDOS.stream().filter(e -> Objects.equals(e.getRoomId(), f.getRoomId())).map(OrderBodyDO::getRoomName).findAny().orElse(null));
        });

        return  plateResLists;

    }


    @Override
    public List<OrderRoomBodyList> getOrderRoomBody(List<Long> orderIds) {


        return goodsMapper.selectNoPlanRoomBody(orderIds,getUserOrganId());


    }


    @Override
    public List<PlanActualGoodsResp> getPlanGoodsList(SavePlanPlateList savePlanPlateList) {

        if(savePlanPlateList.getMixConfig() == null){
            return new ArrayList<>();
        }

        Integer mixedType = Optional.ofNullable(savePlanPlateList.getMixConfig().getMixedType()).orElse(MixConfigTypeEnum.SAMEMATERIALANDTHICKNESS.getType());

        List<SavePlanPlateList.GoodsItemList> goodsItemListList = Optional.ofNullable(savePlanPlateList.getGoodsItemList()).orElseThrow(()-> new ServiceException(PLAN_PLATE_DATA_ERROR));

        List<PlanActualGoodsResp> planActualGoodsResps = new ArrayList<>();

        if(savePlanPlateList.getPlanId() != null){

            Long planId = savePlanPlateList.getPlanId();

            PlanDO planDO = validatePlan(planId);

            if(planDO.getType().equals(PlanTypeEnum.MIXEDORDERPLAN.getType())){
                return Collections.emptyList();
            }

            List<SavePlanPlateList.GoodsItemList> goodsItemLists = planItemMapper.selectPlanGoodsList(planId, getUserOrganId());

            goodsItemLists.forEach(f-> f.setThickness(new BigDecimal(f.getThickness().stripTrailingZeros().toPlainString())));

            goodsItemListList.addAll(goodsItemLists);

        }


        Map<String, List<Long>> grouped;

        if(MixConfigTypeEnum.SAMEMATERIALANDTHICKNESS.getType().equals(mixedType)){

            grouped = groupByMaterialAndThicknessFilterLessThanOne(goodsItemListList);

        }else {

            grouped = groupByThicknessFilterLessThanOne(goodsItemListList);

        }


        grouped.forEach((key,value) ->{

            List<GoodsRespVO> goodsRespVOS = goodsMapper.selectNoOrderPlanGoods(value, getUserOrganId());
            if(goodsRespVOS.size() == 1){
                return;
            }

            planActualGoodsResps.add(
                    PlanActualGoodsResp.builder()
                            .ids(value)
                            .goodsRespVOS(goodsRespVOS)
                            .build()
            );

        });

        return planActualGoodsResps;
    }



    @Override
    public List<PlateResList> getPlanPlateList(Long planId) {

        List<PlateResList> plateResLists = planItemMapper.selectPlateListByPlanId(planId,getUserOrganId());

        if (CollUtil.isEmpty(plateResLists)) {
            return new ArrayList<>();
        }

        List<OptimizeBoardModelDO> boardModelDOS = esUtils.getEsDocument(FIELD_PLANID, planId, ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), OptimizeBoardModelDO.class);

        if(CollUtil.isNotEmpty(boardModelDOS)) {

            List<RemainBoardInfo> remainBoardInfos = new ArrayList<>();

            boardModelDOS.forEach(f -> {

                if (f.getRemainBoardInfo() != null) {
                    try {
                        remainBoardInfos.addAll(new ArrayList<>(parseArray(f.getRemainBoardInfo(), RemainBoardInfo.class)));

                    } catch (Exception e) {
                        log.error(e.getMessage());
                        throw exception(PLAN_PLATE_OPTIMIZE_DATA_ERROR);
                    }

                }

            });

            List<RemainBoardInfo.PlaceBoardList> placeBoardList = new ArrayList<>();

            remainBoardInfos.forEach(f-> placeBoardList.addAll(f.getPlaceBoardList()));

            if(CollUtil.isNotEmpty(placeBoardList)){

                List<RemainBoardInfo.Blocks> blocksList = new ArrayList<>();
                placeBoardList.forEach(f->blocksList.addAll(f.getBlocks()));

                if(CollUtil.isNotEmpty(blocksList)){

                    plateResLists.forEach(f->{

                        f.setBoardId(blocksList.stream().filter(b->b.getBlockId().equals(f.getPlateNo())).findFirst().orElse(new RemainBoardInfo.Blocks()).getBoardId());

                        f.setPlaceId(blocksList.stream().filter(b->b.getBlockId().equals(f.getPlateNo())).findFirst().orElse(new RemainBoardInfo.Blocks()).getPlaceId());

                    });

                }

            }

        }

        return plateResLists;

    }



    private PlanDO validatePlan(Long id) {

        PlanDO planDO = planMapper.selectById(id);

        AssertUtils.notEmpty(planDO,PLAN_NOT_EXISTS);

        return planDO;
    }




    private List<PlanDO> validatePlanExists(List<Long> planIds ) {

        List<PlanDO> planDOS = new ArrayList<>();
        for (Long planId : planIds) {
            PlanDO planDO = planMapper.selectById(planId);
            if(ObjectUtils.isEmpty(planDO)){
                throw exception(PLAN_NOT_DATA_EXISTS,planId);
            }
            planDOS.add(planDO);
        }

        return planDOS;
    }




    // 方案信息
    private String getProcessName(Long processId,List<SystemConfigRespDTO> configDOS) {
        return configDOS.stream()
                .filter(f -> Objects.equals(f.getId(), processId))
                .map(SystemConfigRespDTO::getName)
                .findAny()
                .orElse(null);
    }

    // 板材信息列表
    private List<PlateInfoVO> getPlateInfoList(Long planId, List<PlateInfoVO> infoVOList) {
        return infoVOList.stream()
                .filter(f -> Objects.equals(f.getPlanId(), planId))
                .toList();
    }

    // 小板总数量
    private long getPlateNum(Long planId, List<PlateInfoVO> infoVOList) {
        return infoVOList.stream()
                .filter(f -> Objects.equals(f.getPlanId(), planId))
                .mapToLong(PlateInfoVO::getCount)
                .sum();
    }

    // 大板优化数量
    private long getOptimizeGoodsCount(Long planId, List<OptimizeBoardModelDO> optimizeBoardModelDos) {
        return optimizeBoardModelDos.stream()
                .filter(f -> Objects.equals(f.getPlanId(), planId))
                .mapToLong(OptimizeBoardModelDO::getBoardCount)
                .sum();
    }

    // 大板开料数
    private long getCutGoodsCount(Long planId, List<OptimizeBoardModelDO> optimizeBoardModelDos) {
        return optimizeBoardModelDos.stream()
                .filter(f -> Objects.equals(f.getPlanId(), planId))
                .mapToLong(m -> m.getCutedBoardNumber() == null ? 0 : m.getCutedBoardNumber())
                .sum();
    }

    // 小板优化数量
    private long getOptimizePlateCount(Long planId, List<OptimizeBoardModelDO> optimizeBoardModelDos) {
        return optimizeBoardModelDos.stream()
                .filter(f -> Objects.equals(f.getPlanId(), planId))
                .mapToLong(m -> m.getPlaceBlockNumber() == null ? 0 : m.getPlaceBlockNumber())
                .sum();
    }

    // 生产单信息
    private List<PlanOrderRespVO> getOrderList(Long planId, List<PlanOrderRespVO> planOrderRespVos) {
        return planOrderRespVos.stream()
                .filter(f -> Objects.equals(f.getPlanId(), planId))
                .toList();
    }



    // 排单翻页
    private void planPageTurning(PlanPageReqVO pageReqVO,IPage<PlanDO> planDOPageResult) {

        PlanDO planDO = planMapper.selectById(pageReqVO.getId());

        AssertUtils.notEmpty(planDO,ORDER_PLAN_DATE_ERROR);

        List<PlanDO> plan;

        // 置顶时进行翻页
        if(Boolean.TRUE.equals(planDO.getIsTop())){

            plan = Boolean.TRUE.equals(pageReqVO.getIsUp()) ?

                    planMapper.selectIsTopUpPlan(planDO.getUpdateTime(), getUserOrganId()) :

                    planMapper.selectIsTopDownPlan(planDO.getUpdateTime(), getUserOrganId());

        }
        // 非置顶进行翻页
        else {

            plan = Boolean.TRUE.equals(pageReqVO.getIsUp()) ?
                    planMapper.selectPlanIds(planDO.getSort(), getUserOrganId()) :

                    planMapper.selectPlanIdsFalse(planDO.getSort(), getUserOrganId());

            if (plan.isEmpty() && Boolean.TRUE.equals(pageReqVO.getIsUp())) {

                List<PlanDO> planDOS = planMapper.selectIsTopPlanList(getUserOrganId());

                AssertUtils.notEmpty(planDOS,NO_PRODUCTION_ORDER_INFORMATION_AVAILABLE,Boolean.TRUE.equals(pageReqVO.getIsUp()) ? "第" : "最后");

                plan.add(planDOS.get(0));
            }

        }

        AssertUtils.notEmpty(plan,NO_PRODUCTION_ORDER_INFORMATION_AVAILABLE,Boolean.TRUE.equals(pageReqVO.getIsUp()) ? "第" : "最后");

        planDOPageResult.setRecords(plan);
        planDOPageResult.setTotal(1L);

    }





    @Transactional(rollbackFor = Exception.class)
    public void deletePlanPlate(List<OrderPlateIds> deleteOrderPlateIds) {
        Long organId = getUserOrganId();

        List<Long> deletePlanIds = deleteOrderPlateIds.stream().map(OrderPlateIds::getPlanId).distinct().toList();

        List<PlanDO> planDOS = validatePlanExists(deletePlanIds);

        List<Long> processIds = new ArrayList<>();
        planDOS.forEach(f->{
            if (f.getStatus().equals(PlanStatusEnum.OPENED.getStatus())) {
                throw exception(PLAN_PLATE_IS_ALL_CUT,f.getId());
            }
            processIds.add(Optional.ofNullable(f.getProcessId()).orElseThrow(() -> exception(PLAN_PROCESS_CONFIG_IS_NULL,f.getId())));
        });

        Integer processSize = systemConfigApi.getProcessSizeByIds(processIds).getCheckedData();

        for (PlanDO planDO : planDOS) {
            if (planDO.getStatus().equals(PlanStatusEnum.OPENED.getStatus())) {
                throw exception(PLAN_PLATE_IS_ALL_CUT,planDO.getId());
            }
        }



        List<Integer> plateStatus = Arrays.asList(OrderPlateCutStatusEnum.OPENING.getStatus(),OrderPlateCutStatusEnum.OPENED.getStatus());

        List<Long> deletePlateIds = deleteOrderPlateIds.stream().map(OrderPlateIds::getPlateId).toList();

        AssertUtils.notEmpty(deletePlateIds,DELETE_PLAN_PLATE_DATA_ERROR);

        Set<Long> deleteOrderIds = deleteOrderPlateIds.stream().map(OrderPlateIds::getOrderId).collect(Collectors.toSet());

        AssertUtils.notEmpty(deleteOrderIds,DELETE_PLAN_PLATE_DATA_ERROR);

        // 查询需要删除的小板对应的信息
        List<PlateDO> plateDOS = plateMapper.selectBatchIds(deletePlateIds);

        AssertUtils.notEmpty(plateDOS,DELETE_PLAN_PLATE_DATA_ERROR);

        List<PlateDO> plateDOList = plateDOS.stream().filter(f -> plateStatus.contains(f.getIsCutted())).toList();

        AssertUtils.empty(plateDOList,PLAN_PLATE_IS_CUTTED);

        // 移除排单板件 & 重新计算订单和材质下的数量和面积
        removePlanOrderAndGoodsPlate(plateDOS, deleteOrderIds);

        // 删除优化生产中未开料的小板的优化数据
        List<OptimizeBoardModelDO> optimizeBoardModelDOS = esUtils.getEsDocument(FIELD_PLANID, deletePlanIds, processSize, ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), OptimizeBoardModelDO.class);

        if(CollUtil.isNotEmpty(optimizeBoardModelDOS)){


            List<String> plateNo = plateDOS.stream().map(PlateDO::getPlateNo).toList();

            optimizeBoardModelDOS.forEach(f->{

                String blockPlaceInfo = Optional.ofNullable(f.getBlockPlaceInfo()).orElseThrow(() -> new ServiceException(ORDER_PLAN_OPTIMIZE_ERROR));

                String remainBoardInfo = Optional.ofNullable(f.getRemainBoardInfo()).orElseThrow(() -> new ServiceException(ORDER_PLAN_OPTIMIZE_ERROR));

                List<PlatePlaceInfo> asJsonObject = new ArrayList<>(parseArray(blockPlaceInfo,PlatePlaceInfo.class));

                List<PlatePlaceInfo> platePlaceInfos = asJsonObject.stream()
                        .filter(fi -> !plateNo.contains(fi.getBo()))
                        .toList();

                f.setBlockPlaceInfo(toJsonString(platePlaceInfos));
                f.setPlaceBlockNumber((long) platePlaceInfos.size());


                List<RemainBoardInfo> remainBoardInfos = new ArrayList<>(parseArray(remainBoardInfo, RemainBoardInfo.class));

                remainBoardInfos.forEach(r->{
                    r.getPlaceBoardList().forEach(p->{
                        p.setBlocks(p.getBlocks().stream()
                                .filter(bl->!plateNo.contains(bl.getBlockId()))
                                .toList());
                    });
                });

                f.setRemainBoardInfo(toJsonString(remainBoardInfos));


            });

            try {

                esDocumentService.bulkUpdate(ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), optimizeBoardModelDOS);

            }catch (Exception e){
                log.error(e.getMessage(), e);
                throw new ServiceException(ES_DATA_ERROR);
            }


        }

        // 删除已无排单板件的排单
        List<Long> deleteEmptyPlanIdList = planMapper.countDeletePlanPlate(deletePlanIds, deleteOrderIds, organId)
                .stream().filter(m -> m.getCount() == 0).map(OrderPlanPlateStatisticsRespVO::getPlanId).distinct().collect(Collectors.toList());

        if (CollUtil.isNotEmpty(deleteEmptyPlanIdList)) {
            planMapper.deleteBatchIds(deleteEmptyPlanIdList);
        }

        // 更新排单下的订单列表
        List<OrderPlanPlateStatisticsRespVO> orderPlanOrderIdList = planMapper.selectPlanOrderIds(deletePlanIds, deleteOrderIds, organId);
        if(CollUtil.isNotEmpty(orderPlanOrderIdList)){
            Map<Long, List<Long>> planIdToOrderIds = orderPlanOrderIdList.stream()
                    .collect(Collectors.groupingBy(
                            row -> row.getPlanId(),
                            Collectors.mapping(OrderPlanPlateStatisticsRespVO::getOrderId, Collectors.toList())
                    ));
            for (Map.Entry<Long, List<Long>> entry : planIdToOrderIds.entrySet()) {
                Long planId = entry.getKey();
                String orderNos = entry.getValue().toString();

                planMapper.update(new LambdaUpdateWrapper<PlanDO>()
                        .eq(PlanDO::getId, planId)
                        .eq(PlanDO::getOrganId, organId)
                        .set(PlanDO::getOrderNos, orderNos));
            }
        }

        // 修改排单加工方案的优化状态
        updateProcessSchemeStatus(deletePlanIds);

        // 删除排单对应的优化生产数据
        if (CollUtil.isNotEmpty(deleteEmptyPlanIdList)) {
            esUtils.deleteEsDocument(FIELD_PLANID, deleteEmptyPlanIdList, ORDER_OPTIMIZE_PLATE_MODEL.getIndex());
        }
    }


    @Transactional(rollbackFor = Exception.class)
    public void insertPlanPlate(List<OrderPlateIds> insertOrderPlateIds,PlanActualGoodsModelDO planActualGoodsModelDO){

        // 后面还需要看看是否需要进行混单的判断
        List<Long> insertPlanIds = insertOrderPlateIds.stream().map(OrderPlateIds::getPlanId).distinct().toList();


        PlanDO planDO = validatePlan(insertPlanIds.get(0));

        Set<Long> orderIds = insertOrderPlateIds.stream().map(OrderPlateIds::getOrderId).collect(Collectors.toSet());

        if(planActualGoodsModelDO != null){

            List<Integer> mixedTypes = insertOrderPlateIds.stream().map(OrderPlateIds::getMixedType).distinct().toList();

            Integer mixedType = mixedTypes.get(0) == null ? 0 : mixedTypes.get(0);

            planDO.setType(PlanTypeEnum.MIXEDORDERPLAN.getType());
            planDO.setFilter(mixedType);

            esUtils.saveEsDocument(PLAN_ACTUAL_GOODS_MODEL.getIndex(), Collections.singletonList(planActualGoodsModelDO));

        }

        // 已开料排单修改为开料中
        if(planDO.getStatus().equals(PlanStatusEnum.OPENED.getStatus())){

            planDO.setStatus(PlanStatusEnum.OPENING.getStatus());
            planDO.setProduceTime(null);
        }

        // 更新排单的订单id列表
        orderIds.addAll(JSON.parseArray(planDO.getOrderNos()).toJavaList(Long.class).stream().distinct().toList());

        planDO.setOrderNos(orderIds.stream().distinct().toList().toString());

        planMapper.update(new LambdaUpdateWrapper<PlanDO>()
                .eq(PlanDO::getOrganId,getUserOrganId())
                .eq(PlanDO::getId,planDO.getId())

                .set(PlanDO::getProduceTime,planDO.getProduceTime())
                .set(PlanDO::getStatus,planDO.getStatus())
                .set(PlanDO::getType,planDO.getType())
                .set(PlanDO::getOrderNos,planDO.getOrderNos())
                .set(PlanDO::getFilter,planDO.getFilter())
        );

        // 新增排单板件 & 重新计算订单和材质下的数量和面积
        insertPlanOrderAndGoodsPlate(planDO.getId(), insertOrderPlateIds, orderIds);

        // 有新添加的小板，修改优化信息的字段信息
        try {

            List<FieldValue> fieldValues = insertPlanIds.stream().map(FieldValue::of).toList();

            UpdateByQueryRequest.Builder builder = new UpdateByQueryRequest.Builder();

            builder.index(ORDER_OPTIMIZE_PLATE_MODEL.getIndex())
                    .query(qb -> qb.bool(bq -> bq.filter(f->{
                                f.terms(t->t.field(FIELD_PLANID).terms(e->e.value(fieldValues)));
                                return f;
                            })
                    ))
                    .script(s -> s.inline(i -> i
                            .source("ctx._source.isNewInsertPlate = params.new_value")
                            .params(ES_FIELD_NEWVALUE, JsonData.of(Boolean.TRUE))
                    ));

            UpdateByQueryRequest request = builder.build();

            elasticsearchClient.updateByQuery(request);

        }catch (Exception e){
            log.error(e.getMessage(), e);
            throw new ServiceException(ES_DATA_ERROR);

        }


        // 修改排单加工方案的优化状态
        updateProcessSchemeStatus(insertPlanIds);

        // 生产单板材造型信息增加排单ID
//        insertPlateModelPlanId(orderIds,insertPlateIds,insertPlanIds.get(0));

    }

    /**
     * 新增排单板件 & 重新计算订单和材质下的数量和面积
     * @param planId
     * @param insertOrderPlateIds
     * @param orderIds
     */
    private void insertPlanOrderAndGoodsPlate(Long planId, List<OrderPlateIds> insertOrderPlateIds, Set<Long> orderIds) {
        // 根据orderGoodsId分组
        Map<Long, List<OrderPlateIds>> groupedPlates = insertOrderPlateIds.stream().collect(Collectors.groupingBy(OrderPlateIds::getId));
        for (Map.Entry<Long, List<OrderPlateIds>> entry : groupedPlates.entrySet()) {
            Long orderGoodsId = entry.getKey();
            List<OrderPlateIds> plates = entry.getValue();

            // 查询version
            GoodsDO goodsDO = goodsMapper.selectById(orderGoodsId);
            List<Long> groupedPlateIdList = plates.stream().map(OrderPlateIds::getPlateId).toList();
            // 排单id更新到小板信息中
            List<List<Long>> batchPlanPlateIdList = batchProcess(groupedPlateIdList);
            for (List<Long> plateIdList : batchPlanPlateIdList) {
                int updatePlateNum = plateMapper.update(new LambdaUpdateWrapper<PlateDO>()
                        .in(PlateDO::getId, plateIdList)
                        .eq(PlateDO::getPlanId, 0L)
                        .set(PlateDO::getPlanId, planId));
                if (updatePlateNum != plateIdList.size()) {
                    throw new ServiceException(CREAT_PLAN_PLATE_DATA_ERROR);
                }

                int version = goodsDO.getVersion();
                // 统计每个order_goods下的未排单面积，更新到order_goods中
                OrderPlanPlateNumAndAreaRespVO goodsPlateNumAndAreaPlanInfo = goodsMapper.selectPlannedPlateNumAndArea(orderGoodsId);
                int update = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                        .eq(GoodsDO::getId, orderGoodsId)
                        .eq(GoodsDO::getVersion, version)
                        .set(GoodsDO::getPlannedPlateArea, goodsPlateNumAndAreaPlanInfo.getArea())
                        .set(GoodsDO::getPlannedPlateNum, goodsPlateNumAndAreaPlanInfo.getNum())
                        .set(GoodsDO::getVersion, version + 1));
                if (update == 0) {
                    throw ServiceExceptionUtil.exception(ErrorCodeConstants.GOODS_PLATE_UPDATE_CONCURRENCY_ERROR, orderGoodsId);
                }
            }
        }

        // 统计更新orders的小板数量和面积
        computeOrderInfo(orderIds);
    }

    /**
     * 移除排单板件 & 重新计算订单和材质下的数量和面积
     *
     * @param plateDOS
     * @param orderIds
     */
    private void removePlanOrderAndGoodsPlate(List<PlateDO> plateDOS, Set<Long> orderIds) {
        // 根据planId分组
        Map<Long, List<PlateDO>> groupedPlates = plateDOS.stream().collect(Collectors.groupingBy(PlateDO::getPlanId));
        for (Map.Entry<Long, List<PlateDO>> entry : groupedPlates.entrySet()) {
            Long planId = entry.getKey();
            List<PlateDO> plates = entry.getValue();

            // 根据goodsId分组
            Map<Long, List<PlateDO>> groupedPlatesByGoodsId = plates.stream().collect(Collectors.groupingBy(PlateDO::getGoodsId));
            for (Map.Entry<Long, List<PlateDO>> entryByGoodsId : groupedPlatesByGoodsId.entrySet()) {
                Long orderGoodsId = entryByGoodsId.getKey();
                List<PlateDO> platesByGoodsId = entryByGoodsId.getValue();

                // 查询version
                GoodsDO goodsDO = goodsMapper.selectById(orderGoodsId);
                List<Long> groupedPlateIdList = platesByGoodsId.stream().map(PlateDO::getId).toList();
                // 排单id更新到小板信息中
                List<List<Long>> batchPlanPlateIdList = batchProcess(groupedPlateIdList);
                for (List<Long> plateIdList : batchPlanPlateIdList) {
                    int updatePlateNum = plateMapper.update(new LambdaUpdateWrapper<PlateDO>()
                            .in(PlateDO::getId, plateIdList)
                            .eq(PlateDO::getPlanId, planId)
                            .set(PlateDO::getPlanId, 0L)
                            .set(PlateDO::getIsOptimized, false));
                    if (updatePlateNum != plateIdList.size()) {
                        throw new ServiceException(CREAT_PLAN_PLATE_DATA_ERROR);
                    }
                }

                int version = goodsDO.getVersion();
                // 统计每个order_goods下的未排单面积，更新到order_goods中
                OrderPlanPlateNumAndAreaRespVO goodsPlateNumAndAreaPlanInfo = goodsMapper.selectPlannedPlateNumAndArea(orderGoodsId);
                int update = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                        .eq(GoodsDO::getId, orderGoodsId)
                        .eq(GoodsDO::getVersion, version)
                        .set(GoodsDO::getPlannedPlateArea, goodsPlateNumAndAreaPlanInfo.getArea())
                        .set(GoodsDO::getPlannedPlateNum, goodsPlateNumAndAreaPlanInfo.getNum())
                        .set(GoodsDO::getVersion, version + 1));
                if (update == 0) {
                    throw ServiceExceptionUtil.exception(ErrorCodeConstants.GOODS_PLATE_UPDATE_CONCURRENCY_ERROR, orderGoodsId);
                }
            }
        }

        // 统计更新orders的小板数量和面积
        computeOrderInfo(orderIds);
    }

    public static Map<String, List<Long>> groupByMaterialAndThickness(List<SavePlanPlateList.GoodsItemList> items) {
        Map<String, List<Long>> groupedItems = new HashMap<>();

        for (SavePlanPlateList.GoodsItemList item : items) {
            // 将 material 和 thickness 组合成键
            String key = item.getMaterial() + "|" + item.getThickness();

            // 如果键不存在，初始化一个新的列表
            groupedItems.putIfAbsent(key, new ArrayList<>());

            // 添加当前项的 id 到对应的分组
            groupedItems.get(key).add(item.getId());
        }

        return groupedItems;
    }

    public static Map<String, List<Long>> groupByThickness(List<SavePlanPlateList.GoodsItemList> items) {
        Map<String, List<Long>> groupedItems = new HashMap<>();

        for (SavePlanPlateList.GoodsItemList item : items) {
            // 将 material 和 thickness 组合成键
            String key = item.getThickness().toString();

            // 如果键不存在，初始化一个新的列表
            groupedItems.putIfAbsent(key, new ArrayList<>());

            // 添加当前项的 id 到对应的分组
            groupedItems.get(key).add(item.getId());
        }


        return groupedItems;
    }




    public static Map<String, List<Long>> groupByMaterialAndThicknessFilterLessThanOne(List<SavePlanPlateList.GoodsItemList> items) {
        Map<String, List<Long>> groupedItems = new HashMap<>();

        for (SavePlanPlateList.GoodsItemList item : items) {
            // 将 material 和 thickness 组合成键
            String key = item.getMaterial() + "|" + item.getThickness();

            // 如果键不存在，初始化一个新的列表
            groupedItems.putIfAbsent(key, new ArrayList<>());

            // 添加当前项的 id 到对应的分组
            groupedItems.get(key).add(item.getId());
        }

        // 过滤掉只包含一个 id 的分组（可选，视需求）
        groupedItems.entrySet().removeIf(entry -> entry.getValue().size() < 2);

        return groupedItems;
    }


    public static Map<String, List<Long>> groupByThicknessFilterLessThanOne(List<SavePlanPlateList.GoodsItemList> items) {
        Map<String, List<Long>> groupedItems = new HashMap<>();

        for (SavePlanPlateList.GoodsItemList item : items) {
            // 将 material 和 thickness 组合成键
            String key = item.getThickness().toString();

            // 如果键不存在，初始化一个新的列表
            groupedItems.putIfAbsent(key, new ArrayList<>());

            // 添加当前项的 id 到对应的分组
            groupedItems.get(key).add(item.getId());
        }

        // 过滤掉只包含一个 id 的分组（可选，视需求）
        groupedItems.entrySet().removeIf(entry -> entry.getValue().size() < 2);

        return groupedItems;
    }


    public static Map<String, List<Long>> groupByMaterialAndThicknessEqualToOne(List<SavePlanPlateList.GoodsItemList> items) {
        Map<String, List<Long>> groupedItems = new HashMap<>();

        for (SavePlanPlateList.GoodsItemList item : items) {
            // 将 material 和 thickness 组合成键
            String key = item.getMaterial() + "|" + item.getThickness();

            // 如果键不存在，初始化一个新的列表
            groupedItems.putIfAbsent(key, new ArrayList<>());

            // 添加当前项的 id 到对应的分组
            groupedItems.get(key).add(item.getId());
        }

        // 只留混单的材质
        groupedItems.entrySet().removeIf(entry -> entry.getValue().size() > 1);

        return groupedItems;
    }


    public static Map<String, List<Long>> groupByThicknessEqualToOne(List<SavePlanPlateList.GoodsItemList> items) {
        Map<String, List<Long>> groupedItems = new HashMap<>();

        for (SavePlanPlateList.GoodsItemList item : items) {
            // 将 material 和 thickness 组合成键
            String key = item.getThickness().toString();

            // 如果键不存在，初始化一个新的列表
            groupedItems.putIfAbsent(key, new ArrayList<>());

            // 添加当前项的 id 到对应的分组
            groupedItems.get(key).add(item.getId());
        }

        // 只留混单的材质
        groupedItems.entrySet().removeIf(entry -> entry.getValue().size() > 1);

        return groupedItems;
    }




    public static Map<String, List<Long>> groupByMaterialAndThicknessAndColorAndBrand(List<SavePlanPlateList.GoodsItemList> items) {
        Map<String, List<Long>> groupedItems = new HashMap<>();

        for (SavePlanPlateList.GoodsItemList item : items) {
            // 将 material 和 thickness 组合成键
            String key = item.getMaterial() + "|" + item.getThickness() + "|" + item.getColor()+ "|" + item.getBrand() + "|" + item.getTexture();

            // 如果键不存在，初始化一个新的列表
            groupedItems.putIfAbsent(key, new ArrayList<>());

            // 添加当前项的 id 到对应的分组
            groupedItems.get(key).add(item.getId());
        }


        return groupedItems;
    }



    // 混单时判断是否进行板材对应并创建排单
    private void mixConfigPlateCorresponding(List<PlanActualGoodsList> planActualGoodsLists,
                                             List<SavePlanPlateList.PlateItemList> plateItemList,
                                             List<SavePlanPlateList.GoodsItemList> goodsItemList,
                                             List<PlanDO> planDOS,
                                             Long processId,
                                             LoginUser loginUser,
                                             Integer mixedType){


        Map<String, List<Long>> grouped;

        if(CollUtil.isNotEmpty(planActualGoodsLists)) {

            // 有选实际大板的小板加大板创建排单
            getCreatePlanMultipleGoodsList(planActualGoodsLists, plateItemList, planDOS, processId, loginUser);

            // 没有选择实际大板的创建排单
            if(MixConfigTypeEnum.SAMEMATERIALANDTHICKNESS.equals(mixedType)){
                // 筛选出材质和厚度相同的大板
                grouped = groupByMaterialAndThicknessEqualToOne(goodsItemList);

            }else {

                grouped = groupByThicknessEqualToOne(goodsItemList);
            }

            if(CollUtil.isNotEmpty(grouped)) {
                createPlanAndPlateDataList(grouped, plateItemList, planDOS, processId, loginUser, PlanTypeEnum.ORDERPLAN.getType());
            }

        }else {

            if(MixConfigTypeEnum.SAMEMATERIALANDTHICKNESS.equals(mixedType)){
                // 筛选出材质和厚度相同的大板
                grouped = groupByMaterialAndThickness(goodsItemList);

            }else {
                // 筛选出厚度相同的大板
                grouped = groupByThickness(goodsItemList);

            }

            createPlanAndPlateDataList(grouped, plateItemList, planDOS, processId, loginUser, PlanTypeEnum.ORDERPLAN.getType());

        }


        planDOS.forEach(f->f.setFilter(f.getType().equals(PlanTypeEnum.MIXEDORDERPLAN.getType()) ? mixedType : 0));


    }





    private void updateProcessSchemeStatus(List<Long> planIds) {

        try {

            UpdateByQueryRequest.Builder builder = new UpdateByQueryRequest.Builder();

            List<FieldValue> fieldValues = planIds.stream().map(FieldValue::of).toList();

            builder.index(PLAN_PROCESS_SCHEME_OPTIMIZE_MODEL.getIndex())
                    .query(qb -> qb.bool(bq -> bq
                            .filter(mq -> mq.terms(tq -> tq.field(FIELD_PLANID).terms(t->t.value(fieldValues))))
                            .filter(mq -> mq.match(mtq -> mtq.field("machineType").query(MachineTypeEnum.CUTTING.getType())))
                    ))
                    .script(s -> s.inline(i -> i
                            .source("ctx._source.isOptimized = params.new_value")
                            .params(ES_FIELD_NEWVALUE, JsonData.of(false))
                    ));

            UpdateByQueryRequest request = builder.build();

            // 执行更新操作
            elasticsearchClient.updateByQuery(request);

        }catch (Exception e){
            log.error(ORDER_PLAN_PROCESS_SCHEME_STATUS_UPDATE_ERROR_MSG + e.getMessage(), e);
            throw new ServiceException(DATA_DATA_ERROR);

        }

    }




    private void insertPlateModelPlanId(List<Long> orderIds,List<Long> plateIds,Long planId) {

        try {

            UpdateByQueryRequest.Builder builder = new UpdateByQueryRequest.Builder();

            List<FieldValue> fieldValues1 = orderIds.stream().map(FieldValue::of).toList();
            List<FieldValue> fieldValues2 = plateIds.stream().map(FieldValue::of).toList();

            builder.index(ORDER_PLATE_MODEL.getIndex())
                    .query(qb -> qb.bool(bq -> bq
                            .filter(f->f.terms(t->t.field("orderId").terms(ts->ts.value(fieldValues1))))
                            .filter(f->f.terms(t->t.field("plateId").terms(ts->ts.value(fieldValues2))))
                    ))
                    .script(s -> s.inline(i -> i
                            .source("ctx._source.planId = params.new_value")
                            .params(ES_FIELD_NEWVALUE, JsonData.of(planId))
                    ));

            UpdateByQueryRequest request = builder.build();

            // 执行更新操作
            elasticsearchClient.updateByQuery(request);

        }catch (Exception e){
            log.error(ORDER_PLAN_PROCESS_SCHEME_STATUS_UPDATE_ERROR_MSG + e.getMessage(), e);
            throw new ServiceException(DATA_DATA_ERROR);

        }

    }






    private void removePlateModelPlanId(List<Long> orderIds,List<Long> planIds,List<Long> plateIds) {

        try {

            UpdateByQueryRequest.Builder builder = new UpdateByQueryRequest.Builder();

            List<FieldValue> fieldValues1 = orderIds.stream().map(FieldValue::of).toList();

            List<FieldValue> fieldValues2 = planIds.stream().map(FieldValue::of).toList();

            List<FieldValue> fieldValues3 = plateIds.stream().map(FieldValue::of).toList();

            builder.index(ORDER_PLATE_MODEL.getIndex())
                    .query(qb -> qb.bool(bq -> {
                        bq.filter(f->f.terms(t->t.field("orderId").terms(ts->ts.value(fieldValues1))));
                        bq.filter(f->f.terms(t->t.field(FIELD_PLANID).terms(ts->ts.value(fieldValues2))));

                        if(CollUtil.isNotEmpty(plateIds)){
                            bq.filter(f->f.terms(t->t.field("plateId").terms(ts->ts.value(fieldValues3))));
                        }

                        return bq;
                    }
                    ))
                    .script(s -> s.inline(i -> i
                            .source("ctx._source.planId = params.new_value")
                            .params(ES_FIELD_NEWVALUE, JsonData.of(0))
                    ));

            UpdateByQueryRequest request = builder.build();

            // 执行更新操作
            elasticsearchClient.updateByQuery(request);

        }catch (Exception e){
            log.error(ORDER_PLAN_PROCESS_SCHEME_STATUS_UPDATE_ERROR_MSG + e.getMessage(), e);
            throw new ServiceException(DATA_DATA_ERROR);

        }

    }




    public static void mergeOrderGoodsLists(List<OrderGoodsResp> list1, List<OrderGoodsResp> list2) {
        Map<String, OrderGoodsResp> list1Map = new HashMap<>();
        for (OrderGoodsResp item1 : list1) {
            String key = generateKey(item1);
            list1Map.put(key, item1);
        }

        AtomicLong sort = new AtomicLong(System.currentTimeMillis());

        for (OrderGoodsResp item2 : list2) {
            String key = generateKey(item2);
            OrderGoodsResp matchedItem = list1Map.get(key);
            if (matchedItem != null) {
                if (matchedItem.getOrderIds() == null) {
                    matchedItem.setOrderIds(new ArrayList<>());
                    matchedItem.setSort(String.valueOf(sort.get()));
                }
                if (item2.getOrderIds() != null) {
                    matchedItem.getOrderIds().addAll(item2.getOrderIds());
                    matchedItem.setSort(String.valueOf(sort.get()));
                }
            }
            sort.addAndGet(1);

        }
    }

    private static String generateKey(OrderGoodsResp item) {
        return String.join("|",
                Objects.toString(item.getGoodsName(), ""),
                Objects.toString(item.getMaterial(), ""),
                Objects.toString(item.getColor(), ""),
                Objects.toString(item.getThickness(), ""),
                Objects.toString(item.getBrand(), ""),
                Objects.toString(item.getTexture(), "")
        );
    }



    private void getCreatePlanMultipleGoodsList(List<PlanActualGoodsList> planActualGoodsLists,
                                                List<SavePlanPlateList.PlateItemList> plateItemList,
                                                List<PlanDO> planDOS,
                                                Long processId,
                                                LoginUser loginUser)  {

        Set<Long> orderIdSet = new HashSet<>();

        planActualGoodsLists.forEach(f -> {

            // 查找和大板分类ID相同的小板的数据
            List<SavePlanPlateList.PlateItemList> plateList = plateItemList.stream().filter(p -> f.getIds().contains(p.getId())).toList();

            Set<Long> planOrderIdSet = new HashSet<>();
            Map<Long, List<Long>> goodsPlateIdMap = new HashMap<>();
            for (SavePlanPlateList.PlateItemList savePlanPlate : plateList) {
                Long orderId = savePlanPlate.getOrderId();
                // 累计排单下的订单号
                planOrderIdSet.add(orderId);
                // 累计发生改变的订单号
                orderIdSet.add(orderId);

                // 累计商品id下的订单号
                List<Long> goodsPlateIdList = goodsPlateIdMap.get(savePlanPlate.getId());
                if(CollUtil.isEmpty(goodsPlateIdList)) {
                    goodsPlateIdList = new ArrayList<>();
                    goodsPlateIdList.add(savePlanPlate.getPlateId());
                    goodsPlateIdMap.put(savePlanPlate.getId(), goodsPlateIdList);
                } else {
                    goodsPlateIdList.add(savePlanPlate.getPlateId());
                }
            }

            AtomicLong sort = new AtomicLong(System.currentTimeMillis());
            sort.addAndGet(1);

            Long planId = (Long) identifierGenerator.nextId(null);

            // 大板商品id
            String goodsId = f.getPlanActualGoodsModelDO().getGoodsId();

            // 创建排单
            planDOS.add(PlanDO.builder()
                    .id(planId)
                    .sort(sort.get())
                    .type(PlanTypeEnum.MIXEDORDERPLAN.getType())
                    .processId(processId)
                    .orderNos(planOrderIdSet.toString())
                    .produceTime(null)
                    .status(PlanStatusEnum.NOCUTTING.getStatus())
                    .operator(loginUser.getNickname())
                    .planTime(LocalDateTime.now())
                    .goodsId(goodsId)
                    .build());

            // 更新板件排单id & 统计更新order_goods的数量和面积
            computePlatePlanAndComputeOrderGoodsInfo(planId, goodsPlateIdMap);

        });
        // 统计更新orders的小板数量和面积
        computeOrderInfo(orderIdSet);
    }




    private void createPlanAndPlateDataList(Map<String, List<Long>> grouped,
                                            List<SavePlanPlateList.PlateItemList> plateItemList,
                                            List<PlanDO> planDOS,
                                            Long processId,
                                            LoginUser loginUser,
                                            Integer planType){

        AtomicLong sort = new AtomicLong(System.currentTimeMillis());

        Set<Long> orderIdSet = new HashSet<>();

        // 进行数据处理：value 里为 大板的分类ID
        grouped.forEach((key, value) -> {

            // 查找和大板分类ID相同的小板的数据
            List<SavePlanPlateList.PlateItemList> plateList = plateItemList.stream().filter(p -> value.contains(p.getId())).toList();

            Set<Long> planOrderIdSet = new HashSet<>();
            Map<Long, List<Long>> goodsPlateIdMap = new HashMap<>();
            for (SavePlanPlateList.PlateItemList savePlanPlate : plateList) {
                Long orderId = savePlanPlate.getOrderId();
                // 累计排单下的订单号
                planOrderIdSet.add(orderId);
                // 累计发生改变的订单号
                orderIdSet.add(orderId);

                // 累计商品id下的订单号
                List<Long> goodsPlateIdList = goodsPlateIdMap.get(savePlanPlate.getId());
                if(CollUtil.isEmpty(goodsPlateIdList)) {
                    goodsPlateIdList = new ArrayList<>();
                    goodsPlateIdList.add(savePlanPlate.getPlateId());
                    goodsPlateIdMap.put(savePlanPlate.getId(), goodsPlateIdList);
                } else {
                    goodsPlateIdList.add(savePlanPlate.getPlateId());
                }
            }

            sort.addAndGet(1);

            Long planId = (Long) identifierGenerator.nextId(null);

            // 创建排单
            planDOS.add(PlanDO.builder()
                    .id(planId)
                    .sort(sort.get())
                    .type(planType)
                    .processId(processId)
                    .orderNos(planOrderIdSet.toString())
                    .produceTime(null)
                    .status(PlanStatusEnum.NOCUTTING.getStatus())
                    .operator(loginUser.getNickname())
                    .planTime(LocalDateTime.now())
                    .build());

            // 更新板件排单id & 统计更新order_goods的数量和面积
            computePlatePlanAndComputeOrderGoodsInfo(planId, goodsPlateIdMap);
        });
        // 统计更新orders的小板数量和面积
        computeOrderInfo(orderIdSet);
    }

    /**
     * 更新板件排单id & 统计更新order_goods的小板数量和面积
     *
     * @param planId
     * @param goodsPlateIdMap
     */
    public void computePlatePlanAndComputeOrderGoodsInfo(Long planId, Map<Long, List<Long>> goodsPlateIdMap) {
        for (Map.Entry<Long, List<Long>> entry : goodsPlateIdMap.entrySet()) {
            Long orderGoodsId = entry.getKey();
            // 查询version
            GoodsDO goodsDO = goodsMapper.selectById(orderGoodsId);
            List<Long> groupedPlateIdList = entry.getValue();
            // 排单id更新到小板信息中
            List<List<Long>> batchPlanPlateIdList = batchProcess(groupedPlateIdList);
            for (List<Long> plateIdList : batchPlanPlateIdList) {
                int updatePlateNum = plateMapper.update(new LambdaUpdateWrapper<PlateDO>()
                        .in(PlateDO::getId, plateIdList)
                        .eq(PlateDO::getPlanId, 0)
                        .set(PlateDO::getPlanId, planId));
                if (updatePlateNum != plateIdList.size()) {
                    throw new ServiceException(CREAT_PLAN_PLATE_DATA_ERROR);
                }
            }

            int version = goodsDO.getVersion();
            // 统计每个order_goods下的未排单面积，更新到order_goods中
            OrderPlanPlateNumAndAreaRespVO goodsPlateNumAndAreaPlanInfo = goodsMapper.selectPlannedPlateNumAndArea(orderGoodsId);
            int update = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                    .eq(GoodsDO::getId, orderGoodsId)
                    .eq(GoodsDO::getVersion, version)
                    .set(GoodsDO::getPlannedPlateArea, goodsPlateNumAndAreaPlanInfo.getArea())
                    .set(GoodsDO::getPlannedPlateNum, goodsPlateNumAndAreaPlanInfo.getNum())
                    .set(GoodsDO::getVersion, version + 1));
            if (update == 0) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.CREATE_PLAN_GOODS_UPDATE_CONCURRENCY_ERROR, orderGoodsId);
            }
        }
    }

    /**
     * 统计更新orders的小板数量和面积
     *
     * @param orderIdSet
     */
    public void computeOrderInfo(Set<Long> orderIdSet) {
        for (Long orderId : orderIdSet) {
            // 统计每个orders下的未排单面积，更新到orders中
            OrderDO orderDO = orderMapper.selectById(orderId);
            OrderPlanPlateNumAndAreaRespVO orderPlateNumAndAreaPlanInfo = orderMapper.selectPlannedPlateNumAndArea(orderId);

            // 当前生产单下排单的板件数量
            Integer num = orderPlateNumAndAreaPlanInfo.getNum();

            int version = orderDO.getVersion();
            LambdaUpdateWrapper<OrderDO> updateWrapper = new LambdaUpdateWrapper<OrderDO>()
                    .eq(OrderDO::getId, orderId)
                    .eq(OrderDO::getVersion, version)
                    .set(OrderDO::getPlannedPlateArea, orderPlateNumAndAreaPlanInfo.getArea())
                    .set(OrderDO::getPlannedPlateNum, num)
                    .set(OrderDO::getVersion, version + 1);
            Integer orderDOStatus = orderDO.getStatus();
            // 更新生产单排单状态
            if (ObjectUtil.notEqual(orderDOStatus, OrderStatusEnum.IN_PRODUCTION.getStatus()) && ObjectUtil.notEqual(orderDOStatus, OrderStatusEnum.SORTED.getStatus())) {
                // 如果生产单下已经没有排单板件，更新生产单为新单
                if (num == 0) {
                    updateWrapper.set(OrderDO::getStatus, OrderStatusEnum.NEW_ORDER.getStatus());
                } else {
                    updateWrapper.set(OrderDO::getStatus, OrderStatusEnum.SORTED.getStatus());
                }
            }
            int update = orderMapper.update(updateWrapper);
            if (update == 0) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.CREATE_PLAN_ORDER_UPDATE_CONCURRENCY_ERROR, orderId);
            }
        }
    }

    /**
     * 拆分list，每个1000条
     *
     * @param dataList
     * @param <T>
     * @return
     */
    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 += 1000) {
            int end = Math.min(i + 1000, total);
            List<T> subList = dataList.subList(i, end);

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

}