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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PageUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch.core.CountRequest;
import co.elastic.clients.elasticsearch.core.CountResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.UpdateByQueryRequest;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.sql.TranslateRequest;
import co.elastic.clients.elasticsearch.sql.TranslateResponse;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpMappingException;
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.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.cf.imes.framework.common.enums.OrderPlateCutStatusEnum;
import com.cf.imes.framework.common.enums.OrderStatusEnum;
import com.cf.imes.framework.common.enums.PlanStatusEnum;
import com.cf.imes.framework.common.enums.PlanTypeEnum;
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.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.orderParts.vo.OrderPartsRemark;
import com.cf.imes.module.executor.controller.admin.plan.bo.OrderIds;
import com.cf.imes.module.executor.controller.admin.plan.bo.OrderSource;
import com.cf.imes.module.executor.controller.admin.plan.bo.PlanProcessSchemeList;
import com.cf.imes.module.executor.controller.admin.plan.bo.ProcessGroupList;
import com.cf.imes.module.executor.controller.admin.plan.saveOptimize.*;
import com.cf.imes.module.executor.controller.admin.plan.vo.*;
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.orderGroup.OrderGroupDO;
import com.cf.imes.module.executor.dal.dataobject.ordermodel.OrderModelDO;
import com.cf.imes.module.executor.dal.dataobject.ordermodel.OrderPlateModel;
import com.cf.imes.module.executor.dal.dataobject.plan.PlanDO;
import com.cf.imes.module.executor.dal.dataobject.plate.PlateDO;
import com.cf.imes.module.executor.dal.dataobject.plate.PlateGoodDO;
import com.cf.imes.module.executor.dal.dataobject.remainplaten.RemainPlateDO;
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.orderGroup.OrderGroupMapper;
import com.cf.imes.module.executor.dal.mysql.orderParts.OrderPartsMapper;
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.PlateGoodMapper;
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.util.elasticsearch.EsUtils;
import com.cf.imes.module.system.api.dataSource.DataSourceApi;
import com.cf.imes.module.system.api.machine.MachineApi;
import com.cf.imes.module.system.api.permission.PermissionApi;
import com.cf.imes.module.system.api.systemconfig.SystemConfigApi;
import com.cf.imes.module.system.api.systemconfig.dto.ProcessSchemeDTO;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.annotation.Resource;
import java.io.IOException;
import java.text.SimpleDateFormat;
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.*;
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.*;

/**
 * @author there
 */
@Service
@Slf4j
@RefreshScope
public class OptimizePlanServiceImpl implements OptimizePlanService {

    @Resource
    private PlanMapper planMapper;

    @Resource
    private RemainPlateMapper remainPlateMapper;

    @Resource
    private PlateMapper plateMapper;

    @Resource
    private GoodsMapper goodsMapper;

    @Resource
    private DataSourceApi dataSourceApi;

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private ElasticsearchClient elasticsearchClient;


    @Resource
    private MachineApi machineApi;


    @Resource
    private SystemConfigApi systemConfigApi;

    @Resource
    private PermissionApi permissionApi;


    @Resource
    private EsUtils esUtils;

    @Resource
    private ESDocumentService esDocumentService;

    @Resource
    private OrderPartsMapper orderPartsMapper;

    @Resource
    private OrderGroupMapper orderGroupMapper;

    @Resource
    private PlanItemMapper planItemMapper;

    @Resource
    private PlateGoodMapper plateGoodMapper;

    @Value("${chenfeng.plan.create.plate-threshold:20000}")
    private int planCreatePlateThreshold;

    private static final String FIELD_ORDER_ID = "orderId";
    private static final String FIELD_PLATE_ID = "plateId";
    private static final String FIELD_PLAN_ID = "planId";
    private static final String FIELD_GOODS_ID = "goodsId";
    private static final String FIELD_SORT = " order by createTime desc ";
    private static final String FIELD_IS_OPTIMIZED = " op.is_optimized";
    private static final String FIELD_PLAN_CONFIG_ID = "planConfigId";
    private static final String FIELD_PROCESS_ID = "processId";
    private static final String FIELD_UPDATETIME = "updateTime";

    private static final String ES_ORDER_MODEL_DATA_ERROR = "生产单板材造型信息ES文档查询数据异常: ";

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public List<PlateOptimize> getPlateListByPlanId(Long planId) {
        return planMapper.selectPlateListByPlanId(planId,getUserOrganId());
    }

    @Override
    public Boolean addRemain(AddRemainReqVO vo) {
        PlanDO planDO = planMapper.selectById(vo.getPlanId());
        if (Objects.isNull(planDO)) {
            throw exception(PLAN_NOT_EXISTS);
        }
        RemainPlateDO remainPlateDO = RemainPlateDO.builder()
                .width(vo.getWidth())
                .length(vo.getLength())
                .planId(vo.getPlanId())
                .initPlanId(vo.getPlanId())
                .goodsId(vo.getGoodsId())
                .name(vo.getGoodsName())
                .material(vo.getMaterial())
                .color(vo.getColor())
                .count(vo.getCount())
                .build();
        remainPlateMapper.insert(remainPlateDO);
        return Boolean.TRUE;
    }




    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCuttingStatus(CuttingStatusRespVO respVO) {

        Long planId = respVO.getPlanId();
        List<String> goodsIds = respVO.getGoodsId();
        List<Integer> goodsNo = respVO.getGoodsNo();
        Integer cutedType = respVO.getCutedType();
        Long planConfigId = respVO.getPlanConfigId();

        if(goodsIds.isEmpty() || goodsNo.isEmpty()){
            return;
        }

        AssertUtils.notEmpty(planConfigId,PLAN_PLATE_OPTIMIZE_DATA_ERROR);

        PlanDO planDO = validatePlanExists(planId);


        // 校验生产单信息和获取 orderId
        String orderNos = Optional.ofNullable(planDO.getOrderNos()).orElseThrow(() -> new ServiceException(ORDER_PLAN_ERROR));

        List<Long> orderIds = new ArrayList<>(JSON.parseArray(orderNos).toJavaList(Long.class).stream().distinct().toList());


        // 查询优化信息
        List<OptimizeBoardModelDO> optimizeBoardModelDOS = esUtils.getEsDocument(FIELD_PLAN_CONFIG_ID,FIELD_PLAN_ID,planConfigId, planId, ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), OptimizeBoardModelDO.class);

        AssertUtils.notEmpty(optimizeBoardModelDOS,ORDER_PLAN_OPTIMIZE_ERROR);

        OptimizeBoardModelDO optimizeBoardModelDO = optimizeBoardModelDOS.get(0);

        // 校验信息
        String remainBoardInfo = optimizeBoardModelDO.getRemainBoardInfo();
        AssertUtils.notEmpty(remainBoardInfo,ORDER_PLAN_OPTIMIZE_ERROR);

        Integer boardCount = optimizeBoardModelDO.getBoardCount();
        AssertUtils.notEmpty(boardCount,ORDER_PLAN_OPTIMIZE_ERROR);

        List<RemainBoardInfo> remainBoardInfos;
        try {

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

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

        AssertUtils.notEmpty(remainBoardInfos,ORDER_PLAN_OPTIMIZE_ERROR);

        Long goodsNum = updatePlanOptimizeData(remainBoardInfos, optimizeBoardModelDO, orderIds, goodsIds, goodsNo, cutedType);


        // 根据开料状态修改排单和生产单的状态
        OptimizePlanServiceImpl o = (OptimizePlanServiceImpl) AopContext.currentProxy();
        o.updateOrderAndPlanByCutStatus(planDO,goodsNum,cutedType,orderIds,boardCount);


    }



    @Override
    public Map<String, Object> getLabelDataSourceValue(GetSourceDataReq req) {
        String sqlStr = dataSourceApi.getSqlById(req.getDataSourceId()).getCheckedData();
        String sql = sqlStr.replace("#{orderId}", req.getOrderId() + "");
        if (!Objects.isNull(req.getPackageId())) {
            sql = sql.replace("#{packageId}", req.getPackageId() + "");
        }
        if (!Objects.isNull(req.getPlanId())) {
            sql = sql.replace("#{planId}", req.getPlanId() + "");
        }
        return orderMapper.selectDynamicSqlString(sql);
    }

    @Override
    public OrderSource getOrderSource(Long orderId, Long planId, Long machineId) {
        if (!Objects.isNull(orderId) && !Objects.isNull(planId)) {
            throw exception(ORDER_PLAN_ID_IS_ONE);
        }
        OrderSource orderSource;
        if (ObjectUtil.isNotNull(orderId)) {
            orderSource = getOrderSourceByOrderId(orderId);
            orderSource.setMachineDTO(machineApi.getMachineDetail(machineId).getCheckedData());
            return orderSource;
        }
        if (ObjectUtil.isNotNull(planId)) {
            orderSource = getOrderSourceByPlanId(planId);
            return orderSource;
        }

        throw exception(ErrorCodeConstants.SOURCE_NOT_EXITS_ERROR);

    }

    @Override
    public OrderSource getOrderSourceByOrderId(Long orderId) {
        OrderDO orderDO = orderMapper.selectById(orderId);

        AssertUtils.notEmpty(orderDO,ORDER_NOT_EXISTS);

        List<GoodsReqVO> goodsDOS = goodsMapper.selectGoodsList(orderId,getUserOrganId());

        AssertUtils.notEmpty(goodsDOS,THIS_ORDER_NOT_PLATE_DATA);

        List<PlateDetailRespVO> plateDOS = plateMapper.selectPlateListByGoodsIds(orderId,getUserOrganId());

        AssertUtils.notEmpty(plateDOS,ORDER_PLATE_ERROR);

        List<OrderModelDO> orderModelDOS = getOrderModels(ListUtil.of(orderId), goodsDOS.stream().map(GoodsReqVO::getId).collect(Collectors.toList()));

        AssertUtils.notEmpty(orderModelDOS,ORDER_PLATE_MODEL_DATE_ERROR);


        // 查询生产单对应的配件信息
        List<PrintOrderPartsRespVO> printOrderPartsRespVOS = orderPartsMapper.selectPartList(Collections.singletonList(orderId), getUserOrganId());


        // 查询生产单对应的配件的ES信息
        List<OrderPartsRemark> orderPartsRemarks = esUtils.getEsDocument(FIELD_ORDER_ID, orderId, printOrderPartsRespVOS.size(), ORDER_PARTS_REMARK_MODEL.getIndex(), OrderPartsRemark.class);


        // 查询生产单对应的包裹信息
        List<PrintOrderPackReqVO> printOrderPackReqVOS = plateMapper.selectOrderPackList(orderId,getUserOrganId());


        if(CollUtil.isNotEmpty(printOrderPackReqVOS)) {
            // 统计每个订单的包装数量
            printOrderPackReqVOS
                    .stream()
                    .filter(f -> f
                            .getOrderId().equals(orderId))
                    .forEach(f -> f.setPackNum(printOrderPackReqVOS.stream().filter(e -> e.getOrderId().equals(orderId)).map(PrintOrderPackReqVO::getPackId).toList().size()));
        }

        return OrderSource.builder()
                .orderList(Collections.singletonList(orderDO))
                .goodsList(goodsDOS)
                .plateList(plateDOS)
                .plateModels(orderModelDOS)
                .orderPartsRespVOS(printOrderPartsRespVOS)
                .orderPartsRemarks(orderPartsRemarks)
                .orderPackReqVOS(printOrderPackReqVOS)
                .build();

    }



    @Override
    public OrderSource getOrderSourceByPlanId(Long planId) {
        PlanDO planDO = planMapper.selectById(planId);

        AssertUtils.notEmpty(planDO,PLAN_NOT_EXISTS);

        // 查询排单对应的大板信息
        List<GoodsReqVO> goodsDOS = goodsMapper.selectGoodsListByOrderIds(planId, getUserOrganId());

        AssertUtils.notEmpty(goodsDOS, PLAN_GOODS_DATA_ERROR);

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

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


        List<Long> ids = goodsDOS.stream().map(GoodsReqVO::getId).toList();

        // 查询排单对应的小板信息
        List<PlateDetailRespVO> plateDetailRespVOS = planItemMapper.selectPlanPlateDetailList(planId,getUserOrganId());

        AssertUtils.notEmpty(plateDetailRespVOS,ORDER_PLAN_PLATE_ERROR);

        for (PlateDetailRespVO plateDetailsRespVO : plateDetailRespVOS) {
            StringBuilder name = new StringBuilder();
            if(!plateDetailsRespVO.getProcessGroupLists().isEmpty()) {
                for (ProcessGroupList groupNameList : plateDetailsRespVO.getProcessGroupLists()) {
                    // 去除最后的逗号
                    name.append(groupNameList.getGroupName()).append(",");

                }
                name = new StringBuilder(name.substring(0, name.length() - 1));
            }
            plateDetailsRespVO.setProcessGroupName(name.toString());

        }

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

        AssertUtils.notEmpty(orderDOS,ORDER_PLAN_ERROR);

        // 需要查询小板造型信息的板材ID
        List<Long> plateIds = plateDetailRespVOS.stream().map(PlateDetailRespVO::getPlateId).toList();

        // 每一千块查询一次es
        List<List<Long>> batchPlateIdsList = batchProcess(plateIds);

        List<OrderModelDO> orderModelDOS = new ArrayList<>();
        for (List<Long> plateIdsList : batchPlateIdsList) {
            orderModelDOS.addAll(esUtils.getEsDocument(FIELD_PLATE_ID, plateIdsList, plateIdsList.size(), ORDER_PLATE_MODEL.getIndex(), OrderModelDO.class));
        }

        AssertUtils.notEmpty(orderModelDOS, ORDER_PLAN_PLATE_MODEL_DATE_ERROR);


        List<RemainPlateDO> remainPlateDOS = remainPlateMapper.selectByPlanId(planId,getUserOrganId());


        List<OptimizeRemainPlate> optimizeRemainPlates = BeanUtils.toBean(remainPlateDOS, OptimizeRemainPlate.class);

        // 查询混单实际请求大板信息
        PlanActualGoodsModelDO planActualGoodsModelDO = null;
        if (planDO.getType().equals(PlanTypeEnum.MIXEDORDERPLAN.getType())) {
            List<PlateGoodDO> plateGoodDOS = plateGoodMapper.selectGoodsByGoodsId(List.of(planDO.getGoodsId()), getUserOrganId());
            if (CollUtil.isNotEmpty(plateGoodDOS)) {
                planActualGoodsModelDO = BeanUtils.toBean(plateGoodDOS.get(0), PlanActualGoodsModelDO.class);
                planActualGoodsModelDO.setPlanId(planId);
            }
        }

        // 查询生产单对应的配件信息
        List<PrintOrderPartsRespVO> printOrderPartsRespVOS = orderPartsMapper.selectPartList(orderIds, getUserOrganId());

        // 查询生产单对应的配件的ES信息
        List<OrderPartsRemark> orderPartsRemarks = esUtils.getEsDocument(FIELD_ORDER_ID, orderIds, printOrderPartsRespVOS.size(), ORDER_PARTS_REMARK_MODEL.getIndex(), OrderPartsRemark.class);

        // 查询生产单对应的包裹信息
        List<PrintOrderPackReqVO> printOrderPackReqVOS = plateMapper.selectPlanPackList(ids,getUserOrganId());


        if(CollUtil.isNotEmpty(printOrderPackReqVOS)) {
            for (Long orderId : orderIds) {
                // 统计每个订单的包装数量
                printOrderPackReqVOS
                        .stream()
                        .filter(f -> f.getOrderId().equals(orderId))
                        .forEach(f -> f.setPackNum(printOrderPackReqVOS.stream()
                                .filter(e -> e.getOrderId().equals(orderId))
                                .map(PrintOrderPackReqVO::getPackId).toList()
                                .size()));
            }
        }

        return OrderSource.builder()
                .orderList(orderDOS)
                .plan(planDO)
                .goodsList(goodsDOS)
                .plateList(plateDetailRespVOS)
                .plateModels(orderModelDOS)
                .planActualGoodsModelDO(planActualGoodsModelDO)
                .optimizeRemainPlates(optimizeRemainPlates)
                .orderPartsRespVOS(printOrderPartsRespVOS)
                .orderPartsRemarks(orderPartsRemarks)
                .orderPackReqVOS(printOrderPackReqVOS)
                .build();
    }



    // 保存优化后的数据
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOptimizationResults(SavePlanPlateResultReqVO req) {

        OptimizeBoardModelDO boardModelDO = req.getOptimizeBoardModelDOS().get(0);

        Long planId = boardModelDO.getPlanId();

        Long processId = 1L;


//        // 延长操作用户的缓存时间
//        redisInsertPlanCreator(planId);
//
//        // 校验是否当前登陆的用户操作排单
//        validatePlanCreator(planId);


        PlanDO planDO = planMapper.selectById(planId);

        AssertUtils.notEmpty(planDO,ORDER_PLAN_DATE_ERROR);

        if(planDO.getStatus().equals(PlanStatusEnum.OPENED.getStatus())){
            throw exception(THIS_PLAN_OPTIMIZE_DATA_ERROR);
        }


        // 兼容数据，后期可删除
        List<OptimizeBoardModelDO> document = esUtils.getEsDocument(FIELD_PLAN_ID, planId, ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), OptimizeBoardModelDO.class);
        if(CollUtil.isNotEmpty(document)){
            OptimizeBoardModelDO optimizeBoardModelDO = document.get(0);
            processId = optimizeBoardModelDO.getProcessId();
        }

        // 更新排单对应小板优化属性
        plateMapper.update(new LambdaUpdateWrapper<PlateDO>()
                .eq(PlateDO::getPlanId, planId)
                .eq(PlateDO::getOrganId, getUserOrganId())
                .set(PlateDO::getIsOptimized, true));

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

            boardModelDO.setProcessId(req.getProcessId());

            if(!planDO.getProcessId().equals(req.getProcessId())) {

                planDO.setProcessId(req.getProcessId());
                planMapper.updateById(planDO);

                // 保存排单对应的方案组的配置信息
                getPlanProcessSchemeConfig(planDO.getProcessId(),planId);

                esUtils.deleteEsDocument(FIELD_PLAN_ID, planId, ORDER_OPTIMIZE_PLATE_MODEL.getIndex());

            }else {
                esUtils.deleteEsDocument(FIELD_PLAN_ID, FIELD_PLAN_CONFIG_ID, planId, boardModelDO.getPlanConfigId(), ORDER_OPTIMIZE_PLATE_MODEL.getIndex());
            }


        }else {

            boardModelDO.setProcessId(planDO.getProcessId());

        }


        if(req.getPlanConfigId() != null && req.getProcessId() == null){
            esUtils.deleteEsDocument(FIELD_PLAN_ID, FIELD_PLAN_CONFIG_ID, planId, req.getPlanConfigId(), ORDER_OPTIMIZE_PLATE_MODEL.getIndex());
        }

        boardModelDO.setIsNewInsertPlate(Boolean.FALSE);


        // 保存优化后的大板数据
        esUtils.saveEsDocument(ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), Collections.singletonList(boardModelDO));


        // ES文档数据刷新
        esUtils.refresh(ORDER_OPTIMIZE_PLATE_MODEL.getIndex());


        if (processId == null) {

            esUtils.deleteEsDocument(FIELD_PLAN_ID, planId, ORDER_OPTIMIZE_PLATE_MODEL.getIndex());
        }

    }





    @Override
    public void saveProcessSchemeResults(SaveProcessSchemeReqVO saveProcessSchemeReqVO) {

        Long processId = saveProcessSchemeReqVO.getProcessId();

        List<SaveProcessSchemeModelDO> modelDOList = saveProcessSchemeReqVO.getModelDOList();

        List<SaveProcessSchemeModelDO> schemeModelDOS = new ArrayList<>(modelDOList.stream().filter(Objects::nonNull).toList());

        SaveProcessSchemeModelDO saveProcessSchemeModelDO = Optional.ofNullable(schemeModelDOS.get(0)).orElseThrow(() -> new ServiceException(THIS_PLAN_SCHEME_DATA_ERROR));

        Long planId = Optional.ofNullable(saveProcessSchemeModelDO.getPlanId()).orElseThrow(() -> new ServiceException(THIS_PLAN_SCHEME_DATA_ERROR));


        PlanDO planDO = planMapper.selectById(planId);

        AssertUtils.notEmpty(planDO,PLAN_NOT_EXISTS);

        List<Long> planConfigIds = schemeModelDOS.stream().map(SaveProcessSchemeModelDO::getPlanConfigId).distinct().toList();

        // 更新排单对应小板优化属性
        plateMapper.update(new LambdaUpdateWrapper<PlateDO>()
                .eq(PlateDO::getPlanId, planId)
                .eq(PlateDO::getOrganId, getUserOrganId())
                .set(PlateDO::getIsOptimized, true));


        // 如果方案组ID不相等，就删除旧的方案组信息,再保存新的加工方案组ID
        if(!planDO.getProcessId().equals(processId)){

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

            // 保存排单对应的加工方案组的配置信息
            getPlanProcessSchemeConfig(processId,planId);

            planMapper.updateById(planDO.setProcessId(processId));

            // 修改方案组，删除全部的方案组的优化信息
            esUtils.deleteEsDocument(FIELD_PLAN_ID,planId,PLAN_PROCESS_SCHEME_OPTIMIZE_MODEL.getIndex());

            // 删除全部的生产优化信息
            esUtils.deleteEsDocument(FIELD_PLAN_ID,planId,ORDER_OPTIMIZE_PLATE_MODEL.getIndex());

            // 保存此时的方案组的优化信息
            esUtils.saveEsDocument(PLAN_PROCESS_SCHEME_OPTIMIZE_MODEL.getIndex(),schemeModelDOS);

            return;

        }

        List<ProcessSchemeModelDO> processSchemeModelDOS = esUtils.getEsDocument(FIELD_PLAN_ID, planId, PLAN_PROCESS_SCHEME_CONFIG.getIndex(), ProcessSchemeModelDO.class);

        // 保存排单对应的加工方案组的配置信息
        if(CollUtil.isEmpty(processSchemeModelDOS)){

            getPlanProcessSchemeConfig(processId,planId);

        }

        esUtils.deleteEsDocument(FIELD_PLAN_ID,FIELD_PLAN_CONFIG_ID,planId,planConfigIds,PLAN_PROCESS_SCHEME_OPTIMIZE_MODEL.getIndex());

        esUtils.saveEsDocument(PLAN_PROCESS_SCHEME_OPTIMIZE_MODEL.getIndex(),schemeModelDOS);

    }






    @Override
    public PlanProcessSchemeList getProcessSchemeConfig(Long processId,Long planId) {

        esUtils.refresh(PLAN_PROCESS_SCHEME_OPTIMIZE_MODEL.getIndex());

        PlanProcessSchemeList planProcessSchemeList = new PlanProcessSchemeList();

        PlanDO planDO = validatePlanExists(planId);

//        List<ProcessSchemeDTO> schemeDTOList = systemConfigApi.getProcessById(processId).getCheckedData();


        List<ProcessSchemeDTO> schemeDTOList;

        // 获取排单对应的方案组的配置信息
        List<ProcessSchemeModelDO> schemeModelDOS = esUtils.getEsDocument(FIELD_PLAN_ID, planId, PLAN_PROCESS_SCHEME_CONFIG.getIndex(), ProcessSchemeModelDO.class);

        if(CollUtil.isNotEmpty(schemeModelDOS)){

            if(!planDO.getProcessId().equals(processId)){
                schemeDTOList = systemConfigApi.getProcessById(processId).getCheckedData();

            }else {

                List<ProcessSchemeDTO> schemeDTOS = BeanUtils.toBean(schemeModelDOS, ProcessSchemeDTO.class);
                schemeDTOList = systemConfigApi.getPlanProcessSchemeConfig(schemeDTOS).getCheckedData();
            }

        }else {

            schemeDTOList = systemConfigApi.getProcessById(processId).getCheckedData();

        }


        planProcessSchemeList.setProcessSchemeList(schemeDTOList);


        // 查询排单对应的方案组信息
        List<SaveProcessSchemeModelDO> saveProcessSchemeReqVOS = esUtils.getEsDocument(FIELD_PLAN_ID, planId, PLAN_PROCESS_SCHEME_OPTIMIZE_MODEL.getIndex(), SaveProcessSchemeModelDO.class);


        if(CollUtil.isNotEmpty(saveProcessSchemeReqVOS)){

            // 查询排单对应的优化信息
            List<OptimizeBoardModelDO> optimizeBoardModelDOS = esUtils.getEsDocument(FIELD_PLAN_ID, planId, schemeDTOList.size(), ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), OptimizeBoardModelDO.class);

            if(CollUtil.isNotEmpty(optimizeBoardModelDOS)){

                saveProcessSchemeReqVOS.forEach(f -> {

                    List<OptimizeBoardModelDO> modelDOList = optimizeBoardModelDOS.stream().filter(o -> o.getPlanConfigId().equals(f.getPlanConfigId())).toList();
                    if (CollUtil.isNotEmpty(modelDOList)) {

                        OptimizeBoardModelDO optimizeBoardModelDO = modelDOList.get(0);

                        String jsonString = toJsonString(optimizeBoardModelDO);

                        f.setPlanOptimize(jsonString);
                        f.setGoodsNum(optimizeBoardModelDO.getBoardCount());
                    }
                });

            }

        }

        planProcessSchemeList.setSaveProcessSchemeReqVOS(saveProcessSchemeReqVOS);


        return planProcessSchemeList;
    }



    //
//        // 查询排单对应的优化信息
//        List<OptimizeBoardModelDO> optimizeBoardModelDOS = esUtils.getEsDocument(FIELD_PLAN_ID, planId, schemeDTOList.size(), ORDER_OPTIMIZE_PLATE_MODEL, OptimizeBoardModelDO.class);
//
//
//        if (CollUtil.isNotEmpty(optimizeBoardModelDOS)) {
//
//
//            if (CollUtil.isNotEmpty(saveProcessSchemeReqVOS)) {
//
//                saveProcessSchemeReqVOS.forEach(f -> {
//
//                    List<OptimizeBoardModelDO> modelDOList = optimizeBoardModelDOS.stream().filter(o -> o.getPlanConfigId().equals(f.getPlanConfigId())).toList();
//                    if (CollUtil.isNotEmpty(modelDOList)) {
//
//                        String jsonString = toJsonString(modelDOList.get(0));
//
//                        f.setPlanOptimize(jsonString);
//                    }
//                });
//            } else {
//                optimizeBoardModelDOS.forEach(f -> {
//
//                    saveProcessSchemeReqVOS.add(SaveProcessSchemeModelDO.builder()
//                            .planConfigId(f.getPlanConfigId())
//                            .planName(f.getPlanConfigName())
//                            .planId(f.getPlanId())
//                            .plateNum(f.getPlaceBlockNumber())
//                            .goodsNum(f.getBoardCount())
//                            .planOptimize(toJsonString(f))
//                            .build());
//
//                });
//            }
//        }



    @Override
    public void updateProcessScheme(PlanProcessSchemeReqVO reqVO) {

        validatePlanExists(reqVO.getPlanId());

        updatePlanProcessScheme(reqVO.getPlanId(),reqVO.getPlanConfigId(),reqVO.getIsLock());


    }




    @Override
    public List<OrderModelDO> getPlateModel(Long orderId) {

        return esUtils.getEsDocumentByScroll(FIELD_ORDER_ID,orderId,1000,"imes_order_plate_model",OrderModelDO.class);

    }






    @Override
    public OrderSource getNoPlanPlateData(NoPlanFilterPlateReqVO reqVO) {

        List<Long> orderIds = Optional.ofNullable(reqVO.getOrderIds()).orElseThrow(() -> new ServiceException(THIS_ORDER_PLATE_DATA_ERROR));

        List<String> goodsIdList = Optional.ofNullable(reqVO.getGoodsIdList()).orElseThrow(() -> new ServiceException(THIS_ORDER_PLATE_DATA_ERROR));

        List<GoodsReqVO> goodsReqVOS = goodsMapper.selectGoodsAndRemainPlate(orderIds,goodsIdList, getUserOrganId());

        AssertUtils.notEmpty(goodsReqVOS,ORDER_GOODS_DATA_ERROR);

        List<Long> orderGoodsIds = goodsReqVOS.stream().map(GoodsReqVO::getId).toList();

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

        AssertUtils.notEmpty(orderDOS,THIS_ORDER_PLATE_DATA_ERROR);

        // 查询未排单的小板数量
        Long unplanPlateNum = plateMapper.selectCount(new LambdaQueryWrapper<PlateDO>()
                .in(PlateDO::getOrderId, orderIds)
                .in(PlateDO::getGoodsId, orderGoodsIds)
                .eq(PlateDO::getPlanId, 0)
                .eq(PlateDO::getDeleted, false));
        if (planCreatePlateThreshold < unplanPlateNum) {
            throw ServiceExceptionUtil.exception(ORDER_PLAN_CREATE_PLATE_NUM_REACH_THRESHOLD_ERROR, planCreatePlateThreshold);
        }

        List<PlateDetailRespVO> plateDetailRespVOS = plateMapper.selectNoPlanPlate(orderIds, orderGoodsIds, getUserOrganId());

        AssertUtils.notEmpty(plateDetailRespVOS,ORDER_PLATE_ERROR);

        // todo 数据结构改后，可以增加排单ID作为筛选，只查为 0 的
        List<OrderModelDO> orderModelDOS = getOrderModels(orderIds, orderGoodsIds);

        AssertUtils.notEmpty(plateDetailRespVOS,ORDER_PLATE_MODEL_DATE_ERROR);

        return OrderSource.builder()
                .orderList(orderDOS)
                .goodsList(goodsReqVOS)
                .plateList(plateDetailRespVOS)
                .plateModels(orderModelDOS)
                .build();

    }

    /**
     * 分页获取生产单ids下的es.orderModels数据
     *
     * @param orderIds
     * @return
     */
    private List<OrderModelDO> getOrderModels(List<Long> orderIds, List<Long> orderGoodsIds) {
        List<OrderModelDO> results = new ArrayList<>();
        try {
            // 查询总数
            CountRequest countRequest = new CountRequest.Builder()
                    .index(ORDER_PLATE_MODEL.getIndex())
                    .query(q -> q.bool(b -> b
                            .filter(f -> f.terms(t -> t
                                    .field(FIELD_ORDER_ID)
                                    .terms(e -> e.value(orderIds.stream().map(FieldValue::of).toList()))
                            ))
                            .filter(f -> f.terms(t -> t
                                    .field("plateGoodsId")
                                    .terms(e -> e.value(orderGoodsIds.stream().map(FieldValue::of).toList()))
                            ))
                    ))
                    .build();
            CountResponse totalResponse = elasticsearchClient.count(countRequest);
            long total = totalResponse.count();

            // 分页查询
            int pageNum = PageUtil.totalPage(total, 1000);

            List<FieldValue> searchAfterSortValues = null;
            for (int page = 1; page <= pageNum; page++) {
                SearchRequest.Builder searchBuilder = new SearchRequest.Builder()
                        .index(ORDER_PLATE_MODEL.getIndex())
                        .size(1000)
                        .sort(s -> s.field(f -> f.field("id.keyword").order(SortOrder.Asc)))
                        .query(q -> q.bool(b -> b
                                .filter(f -> f.terms(t -> t.field(FIELD_ORDER_ID).terms(e -> e.value(orderIds.stream().map(FieldValue::of).toList()))))
                                .filter(f -> f.terms(t -> t.field("plateGoodsId").terms(e -> e.value(orderGoodsIds.stream().map(FieldValue::of).toList()))))
                        ))
                        .source(s -> s
                                .filter(f -> f
                                        .includes("holeDetail", "sideHoleDetail", "contourDetail", "sideDetail", "id", FIELD_PLATE_ID, "pointDetail",
                                                "rawPointDetail", "splitLength", "splitWidth", "offsetX", "offsetY")
                                )
                        )
                        .trackTotalHits(t -> t.enabled(false));

                // 设置 search_after，非第一页时才需要
                if (searchAfterSortValues != null) {
                    searchBuilder.searchAfter(searchAfterSortValues);
                }

                SearchResponse<OrderModelDO> response = elasticsearchClient.search(searchBuilder.build(), OrderModelDO.class);

                List<Hit<OrderModelDO>> hits = response.hits().hits();

                for (Hit<OrderModelDO> hit : hits) {
                    results.add(hit.source());
                }

                // 如果没有更多结果，终止循环
                if (hits.isEmpty()) {
                    break;
                }

                // 获取最后一条记录的 sort 值，用于下一页的 search_after
                searchAfterSortValues = hits.get(hits.size() - 1).sort();

                // 防止 sort 信息缺失导致死循环
                if (searchAfterSortValues == null || searchAfterSortValues.isEmpty()) {
                    break;
                }

            }

            return results;

        } catch (JsonpMappingException e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(ORDER_DATA_ERROR);
        } catch (IOException | ElasticsearchException e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(DATA_DATA_ERROR);
        }
    }



    @Override
    public PageResult<OrderRespVOCopy> getOrderPlateDataFilter(FilterDataPageReqVO pageReqVO) {

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

        IPage<OrderRespVOCopy> orderRespVOCopyIPage = new PageDTO<>();

        AdvancedScreeningConfig filterConfigs = parseObject(pageReqVO.getConfig(), AdvancedScreeningConfig.class);

        AssertUtils.notEmpty(filterConfigs,ADVANCED_SCREENING_CONFIG_ERROR);


        boolean isPlateFilter = CharSequenceUtil.isNotBlank(filterConfigs.getPlateDataFilter());

        // Mysql 的筛选条件
        String plateDataFilter = isPlateFilter ? filterConfigs.getPlateDataFilter() :  "" ;

        // ES 的筛选条件
        String modelDataFilter = filterConfigs.getModelDataFilter();

        // 连接中间运算符
        String logicalOperator = filterConfigs.getLogicalOperator();

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



            // 如果有加工组的字段，SQL查询走加工组表
            if(plateDataFilter.contains("og.name")){

                QueryWrapperX<OrderGroupDO> queryWrapperX = new QueryWrapperX<>();
                queryWrapperX
                        .eq("og.organ_id",getUserOrganId())
                        .eq("og.deleted",false)
                        .eq("ogs.plan_id",0)
                        .eq(FIELD_IS_OPTIMIZED,false)
                        .inIfPresent("ogs.good_id",pageReqVO.getGoodsIds())
                        .apply(plateDataFilter);

                if(CharSequenceUtil.isBlank(modelDataFilter)) {
                    queryWrapperX.orderByDesc("og.order_id");
                    orderRespVOCopyIPage = orderGroupMapper.selectFilterOrderPagePlate(page, queryWrapperX);
                    List<Long> orderIdList = orderRespVOCopyIPage.getRecords().stream().map(OrderRespVOCopy::getOrderId).toList();
                    List<OrderRespVOCopy> orderRespVOCopies = orderMapper.selectOrderListByIds(orderIdList, getUserOrganId());
                    orderRespVOCopyIPage.setRecords(orderRespVOCopies);

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

                orderIdLists.addAll(orderGroupMapper.selectFilterOrderPlateList(queryWrapperX));


            }else {

                QueryWrapperX<OrderDO> queryWrapperX = new QueryWrapperX<>();
                queryWrapperX
                        .eq("o.organ_id", getUserOrganId())
                        .eq("o.deleted", false)
                        .eq("og.plan_id", 0)
                        .inIfPresent("og.good_id",pageReqVO.getGoodsIds())
                        .apply(isPlateFilter,plateDataFilter);

                if (CharSequenceUtil.isBlank(modelDataFilter)) {
                    queryWrapperX.orderByDesc("o.id");
                    IPage<OrderRespVOCopy> orderPlateList = orderMapper.selectFilterOrderPagePlate(page, queryWrapperX);
                    return new PageResult<>(orderPlateList.getRecords(), orderPlateList.getTotal());
                }

                List<Long> orderIds = orderMapper.selectFilterOrderPlateList(queryWrapperX);

                orderIdLists.addAll(orderIds);

            }



        if(CharSequenceUtil.isNotBlank(modelDataFilter)){

            Map<Long, List<OrderPlateModel>> orderPlateModelMap = getOrderPlateModelList(conversion(orderIdLists.toString()), null, logicalOperator, modelDataFilter, pageReqVO,FIELD_ORDER_ID);
            if(orderPlateModelMap.isEmpty()){
                return null;
            }
            for (Map.Entry<Long, List<OrderPlateModel>> entry : orderPlateModelMap.entrySet()) {

                List<OrderRespVOCopy> orderRespVOCopies = orderMapper.selectOrderListByIds(entry.getValue().stream().map(OrderPlateModel::getOrderId).toList(), getUserOrganId());

                orderRespVOCopyIPage.setRecords(orderRespVOCopies);
                orderRespVOCopyIPage.setTotal(entry.getKey());
            }
        }

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

    }



    @Override
    public Boolean getNewPlanPlate(Long planId) {

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

        if(CollUtil.isNotEmpty(optimizeBoardModelDOS)){
            List<OptimizeBoardModelDO> modelDOS = optimizeBoardModelDOS.stream().filter(f ->Boolean.TRUE.equals(f.getIsNewInsertPlate())).toList();
            return modelDOS.isEmpty() ? Boolean.FALSE : Boolean.TRUE;
        }

        return Boolean.FALSE;

    }




    @Override
    public void updateProcessSchemeConfig(PlanSchemeConfigReqVO reqVO) {


        try {

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

            builder.index(PLAN_PROCESS_SCHEME_CONFIG.getIndex())
                    .query(qb -> qb.bool(bq -> bq
                            .must(mq -> mq.term(t->t.field(FIELD_PLAN_ID).value(reqVO.getPlanId())))
                            .must(mq -> mq.match(mtq -> mtq.field(FIELD_PLAN_CONFIG_ID).query(reqVO.getPlanConfigId())))
                    ))
                    .script(s -> s.inline(i -> i
                            .source("""
                                ctx._source.processLineConfig = params.processLineConfig;
                                ctx._source.optimizationConfig = params.optimizationConfig;
                                ctx._source.analyzer = params.analyzer;
                                """)
                            .params("processLineConfig", JsonData.of(reqVO.getProcessLineConfig()))
                            .params("optimizationConfig", JsonData.of(reqVO.getOptimizationConfig()))
                            .params("analyzer", JsonData.of(reqVO.getAnalyzer()))
                    ));

            UpdateByQueryRequest request = builder.build();

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

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

        }

    }




    @Override
    public PageResult<OrderGoodsResp> getGoodsPlateDataFilter(FilterDataPageReqVO pageReqVO) {

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

        IPage<OrderGoodsResp> orderRespVOCopyIPage = new PageDTO<>();

        AdvancedScreeningConfig filterConfigs = parseObject(pageReqVO.getConfig(), AdvancedScreeningConfig.class);

        AssertUtils.notEmpty(filterConfigs,ADVANCED_SCREENING_CONFIG_ERROR);

        // MySql 筛选条件非空判断，方便判断是否进行 SQL 拼接
        boolean isPlateFilter = CharSequenceUtil.isNotBlank(filterConfigs.getPlateDataFilter());

        // Mysql 的筛选条件
        String plateDataFilter = isPlateFilter ? filterConfigs.getPlateDataFilter() : "" ;

        // ES 的筛选条件
        String modelDataFilter = filterConfigs.getModelDataFilter();

        // 连接中间运算符
        String logicalOperator = filterConfigs.getLogicalOperator();


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

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

        // 判断 MySql 的查询条件是否为空

        // 如果有加工组的字段，SQL查询走加工组表
        if (plateDataFilter.contains("og.name")) {

            QueryWrapperX<OrderGroupDO> queryWrapperX = new QueryWrapperX<>();
            queryWrapperX
                    .eq("og.organ_id", getUserOrganId())
                    .eq("og.deleted", false)
                    .eq("ogs.plan_id", 0)
                    .eq(FIELD_IS_OPTIMIZED, false)
                    .inIfPresent("og.id", pageReqVO.getOrderIds())
                    .apply(plateDataFilter);

            if (CharSequenceUtil.isBlank(modelDataFilter)) {
                orderRespVOCopyIPage = orderGroupMapper.selectFilterGoodsPagePlate(page, queryWrapperX);
                List<String> goodsIds = orderRespVOCopyIPage.getRecords().stream().map(OrderGoodsResp::getGoodsId).toList();

                orderRespVOCopyIPage.setRecords(BeanUtils.toBean(goodsMapper.selectListByGoodsIdList(goodsIds, getUserOrganId()), OrderGoodsResp.class));

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

            List<OrderIds> orderIds = orderGroupMapper.selectFilterGoodsPlateList(queryWrapperX);

            orderIdList.addAll(orderIds.stream().map(OrderIds::getOrderId).distinct().toList());

            goodsIdList.addAll(orderIds.stream().map(OrderIds::getGoodsId).distinct().toList());

        } else {

            QueryWrapperX<OrderDO> queryWrapperX = new QueryWrapperX<>();
            queryWrapperX
                    .eq("o.organ_id", getUserOrganId())
                    .eq("o.deleted", false)
                    .eq("og.plan_id", 0)
                    .eq(FIELD_IS_OPTIMIZED, false)
                    .inIfPresent("o.id", pageReqVO.getOrderIds())
                    .apply(isPlateFilter, plateDataFilter);

            // ES筛选条件为空，查询后直接返回
            if (CharSequenceUtil.isBlank(modelDataFilter)) {
                IPage<OrderGoodsResp> orderGoodsRespIPage = orderMapper.selectFilterGoodsPlatePage(page, queryWrapperX);
                return new PageResult<>(orderGoodsRespIPage.getRecords(), orderGoodsRespIPage.getTotal());
            }

            List<OrderIds> orderIds = orderMapper.selectFilterGoodsPlateList(queryWrapperX);

            orderIdList.addAll(orderIds.stream().map(OrderIds::getOrderId).distinct().toList());

            goodsIdList.addAll(orderIds.stream().map(OrderIds::getGoodsId).distinct().toList());

        }


        // 判断 ES 是否为空
        if(CharSequenceUtil.isNotBlank(modelDataFilter)){

            Map<Long, List<OrderPlateModel>> orderModelMap = getOrderPlateModelList(conversion(orderIdList.toString()), conversion(goodsIdList.toString()), logicalOperator, modelDataFilter, pageReqVO,FIELD_GOODS_ID);
            if(orderModelMap.isEmpty()){
                return null;
            }
            for (Map.Entry<Long, List<OrderPlateModel>> entry : orderModelMap.entrySet()) {

                List<String> goodsIds = entry.getValue().stream().map(OrderPlateModel::getGoodsId).toList();

                List<OrderGoodsResp> orderGoodsRests = BeanUtils.toBean(goodsMapper.selectListByGoodsIdList(goodsIds, getUserOrganId()), OrderGoodsResp.class);

                orderRespVOCopyIPage.setRecords(orderGoodsRests);
                orderRespVOCopyIPage.setTotal(entry.getKey());
            }
        }

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




    private Map<Long,List<OrderPlateModel>> getOrderPlateModelList(String orderIdList,
                                                    String goodsIdList,
                                                    String logicalOperator,
                                                    String config,
                                                    FilterDataPageReqVO pageReqVO, String field) {
        try {

            // < 去重后查询到的总数量 ， 查询到的板材造型数据 >
            Map<Long,List<OrderPlateModel>> orderModelMap = new HashMap<>();

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

            builder.index(ORDER_PLATE_MODEL.getIndex());


            // 生成查询 SQL
            String filterSql = generateSql(orderIdList, goodsIdList, logicalOperator, config);


            TranslateRequest.Builder builderTranslate = new TranslateRequest.Builder();

            builderTranslate.query(filterSql);

            TranslateResponse translate = elasticsearchClient.sql().translate(builderTranslate.build());

            builder.query(q->q.constantScore(c->c.filter(translate.query())));


            // 排序
            builder.sort(s->s.field(f->f.field("createTime").order(SortOrder.Desc)));


            // 只查询一个字段
            builder.source(q->q.filter(f->f.includes(field)));


            // 去重
            builder.collapse(q->q.field(field));


            // 分页
            builder.from(((pageReqVO.getPageNo() -1) * pageReqVO.getPageSize()));
            builder.size(pageReqVO.getPageSize());


            // 获取去重后的总数量
            builder.aggregations("distinct_count",agg->agg.cardinality(c->c.field(field)));


            // 执行查询
            SearchResponse<OrderPlateModel> search = elasticsearchClient.search(builder.build(), OrderPlateModel.class);

            Map<String, Aggregate> aggregations = search.aggregations();

            // 获取总数量
            long value = aggregations.get("distinct_count").cardinality().value();

            List<Hit<OrderPlateModel>> hits = search.hits().hits();
            if (CollUtil.isNotEmpty(hits)) {
                orderModelMap.put(value,hits.stream().map(Hit::source).toList());
            }
            return orderModelMap;

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

    private String conversion(String filed) {

        // 去除字段中的 【 】
        return filed.replaceAll("\\[|\\]", "");

    }



    private String generateSql(String orderIdList,String goodsIdList,String logicalOperator,String config){

        String filterSql;

        // 因生成 ES DSL 语句规则限制，无法赋值为空，只能赋值为 0
        String orderIds =  CharSequenceUtil.isNotBlank(orderIdList) ? orderIdList : "0";

        if(CharSequenceUtil.isNotBlank(logicalOperator)){

            if(CharSequenceUtil.isNotBlank(goodsIdList)){

                filterSql = " SELECT orderId,goodsId FROM " + ORDER_PLATE_MODEL.getIndex() + " WHERE  "
                        + "isOptimized = false " +
                        "and ( orderId in ( " + orderIds + " ) and goodsId in ( " + goodsIdList + " ) )"
                        + logicalOperator
                        + config
                        + FIELD_SORT;

            }else {

                filterSql = "SELECT orderId,goodsId FROM " + ORDER_PLATE_MODEL.getIndex() + " WHERE  "
                        + " " +
                        " ( orderId in ( " + orderIds + "))"
                        + logicalOperator
                        + config
                        + FIELD_SORT;

            }
        }else {

            filterSql = "SELECT orderId,goodsId FROM " + ORDER_PLATE_MODEL.getIndex() + " WHERE  "
                    + "isOptimized = false and "
                    + config
                    + FIELD_SORT;

        }

        return filterSql;
    }



    private List<PlateDO> getPlanGoodsPlateList(List<Long> goodsNo,PlanDO planDO,List<OptimizeBoardModelDO> optimizeBoardModelDOS){


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


        AssertUtils.notEmpty(optimizeBoardModelDOS,ORDER_PLAN_OPTIMIZE_ERROR);

        OptimizeBoardModelDO optimizeBoardModelDO = optimizeBoardModelDOS.get(0);

        Boolean isOptimized = optimizeBoardModelDO.getIsOptimized();

        if(Boolean.FALSE.equals(isOptimized)){
            throw exception(ORDER_PLAN_NO_OPTIMIZE_UPDATE_ERROR);
        }


        List<String> plateNo = new ArrayList<>();


        // 创建一个列表存储 BlockPlaceInfo 对象,将优化的小板信息拿出，获取到小板编号，查询生产单，修改生产单的状态
        List<BlockPlaceInfo> outputList = new ArrayList<>();


        String blockPlaceInfo = optimizeBoardModelDO.getBlockPlaceInfo();


        AssertUtils.notEmpty(blockPlaceInfo, ORDER_PLAN_OPTIMIZE_ERROR);


        JsonNode jsonArray = parseTree(blockPlaceInfo);


        boolean isArray = jsonArray.isArray();


        if (Boolean.FALSE.equals(isArray)) {
            throw exception(ORDER_PLAN_OPTIMIZE_ERROR);
        }


        for (JsonNode jsonNode : jsonArray) {
            Long bi = jsonNode.get("bi").asLong();
            String bo = jsonNode.get("bo").asText();
            outputList.add(new BlockPlaceInfo(bi,bo));
        }


        goodsNo.forEach(g->
                plateNo.addAll(outputList.stream().filter(f -> f.getBi().equals(g)).map(BlockPlaceInfo::getBo).distinct().toList())
        );


        List<PlateDO> plateDOS = plateMapper.selectPlateByPlateNo(orderIdList,plateNo.stream().distinct().toList(), getUserOrganId());

        AssertUtils.notEmpty(plateDOS,ORDER_PLAN_OPTIMIZE_ERROR);

        return plateDOS;


    }



    private PlanDO validatePlanExists(Long id ) {
        PlanDO planDO = planMapper.selectById(id);

        AssertUtils.notEmpty(planDO,PLAN_NOT_EXISTS);
        return planDO;
    }




    private void updatePlanProcessScheme(Long planId,Long planConfigId,Boolean isLock) {

        try {

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

            builder.index(ORDER_OPTIMIZE_PLATE_MODEL.getIndex())
                    .query(qb -> qb.bool(bq -> bq.filter(f->{
                        f.term(t->t.field(FIELD_PLAN_ID).value(planId));
                        f.term(t->t.field(FIELD_PLAN_CONFIG_ID).value(planConfigId));
                        return f;
                    })
                    ))
                    .script(s -> s.inline(i -> i
                            .source("ctx._source.isLock = params.new_value")
                            .params("new_value", JsonData.of(isLock))
                    ));

            UpdateByQueryRequest request = builder.build();

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

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

        }

    }






    private Long updatePlanOptimizeData(List<RemainBoardInfo> remainBoardInfos,
                                        OptimizeBoardModelDO optimizeBoardModelDO,
                                        List<Long> orderIds,
                                        List<String> goodsIds,
                                        List<Integer> goodsNo,
                                        Integer cutedType) {


        Date currentDate = new Date();
        AtomicLong goodsCount = new AtomicLong(0L);
        List<String> planNos = new ArrayList<>();

        // 大板开料数量
        long cutedBoardNumber = Optional.ofNullable(optimizeBoardModelDO.getCutedBoardNumber()).orElse(0L);


        // 更新板材状态并统计数量
        updateRemainBoardInfo(remainBoardInfos, goodsIds, goodsNo, cutedType, goodsCount, planNos,cutedBoardNumber);

        // 更新优化数据
        long goodsNum = updateOptimizeData(optimizeBoardModelDO, goodsCount.get(), planNos.size(), cutedType);

        // 同步数据到 ES
        syncWithElasticsearch(remainBoardInfos, optimizeBoardModelDO, currentDate);

        // 更新数据库中的板材信息
        updatePlateData(orderIds, planNos, cutedType);

        return goodsNum;
    }




    private void updateRemainBoardInfo(List<RemainBoardInfo> remainBoardInfos,
                                       List<String> goodsIds,
                                       List<Integer> goodsNo,
                                       Integer cutedType,
                                       AtomicLong goodsCount,
                                       List<String> planNos,
                                       Long cutedBoardNumber) {

        remainBoardInfos.stream()
                .filter(remainBoard -> goodsIds.contains(remainBoard.getGoodsId()))
                .flatMap(remainBoard -> remainBoard.getPlaceBoardList().stream())
                .filter(board -> goodsNo.contains(board.getBoardId()) && !board.getCutedType().equals(cutedType) &&
                        (!cutedType.equals(OrderPlateCutStatusEnum.OPENING.getStatus()) || !board.getCutedType().equals(OrderPlateCutStatusEnum.OPENED.getStatus())))
                .forEach(board -> {
                    if(cutedType.equals(OrderPlateCutStatusEnum.NOCUTTING.getStatus()) ) {
                        if(board.getCutedType().equals(OrderPlateCutStatusEnum.OPENED.getStatus())) {
                            goodsCount.incrementAndGet();
                        }
                        planNos.addAll(board.getBlocks().stream()
                                .map(RemainBoardInfo.Blocks::getBlockId)
                                .toList());
                        board.setCutedType(cutedType);
                        return;
                    }

                    goodsCount.incrementAndGet();
                    board.setCutedType(cutedType);
                    planNos.addAll(board.getBlocks().stream()
                            .map(RemainBoardInfo.Blocks::getBlockId)
                            .toList());

                });


        if(cutedBoardNumber == 0) {

            remainBoardInfos.stream()
                    .filter(remainBoard -> goodsIds.contains(remainBoard.getGoodsId()))
                    .flatMap(remainBoard -> remainBoard.getPlaceBoardList().stream())
                    .filter(board -> goodsNo.contains(board.getBoardId()) && cutedType.equals(OrderPlateCutStatusEnum.OPENED.getStatus()) && board.getCutedType().equals(OrderPlateCutStatusEnum.OPENED.getStatus()))

                    .forEach(f -> goodsCount.incrementAndGet());

        }


    }





    private long updateOptimizeData(OptimizeBoardModelDO optimizeBoardModelDO,
                                    long goodsCount,
                                    int blockCount,
                                    Integer cutedType) {

        long cutedBoardNumber = Optional.ofNullable(optimizeBoardModelDO.getCutedBoardNumber()).orElse(0L);
        long cutedBlockNumber = Optional.ofNullable(optimizeBoardModelDO.getCutedBlockNumber()).orElse(0L);

        if (OrderPlateCutStatusEnum.NOCUTTING.getStatus().equals(cutedType)) {
            cutedBoardNumber -= goodsCount;
            cutedBlockNumber -= blockCount;
        } else if (OrderPlateCutStatusEnum.OPENED.getStatus().equals(cutedType)) {
            cutedBoardNumber += goodsCount;
            cutedBlockNumber += blockCount;
        }

        cutedBoardNumber = cutedBoardNumber < 0 ?  0 : cutedBoardNumber;

        cutedBlockNumber = cutedBlockNumber < 0 ?  0 : cutedBlockNumber;

        optimizeBoardModelDO.setCutedBoardNumber(cutedBoardNumber);
        optimizeBoardModelDO.setCutedBlockNumber(cutedBlockNumber);

        return cutedBoardNumber;
    }

    private void syncWithElasticsearch(List<RemainBoardInfo> remainBoardInfos,
                                       OptimizeBoardModelDO optimizeBoardModelDO,
                                       Date date) {

        remainBoardInfos.forEach(f->{
            f.setPlaceBoardJSON(toJsonString(f.getPlaceBoardList()));
        });

        Map<String, Object> updateData = new HashMap<>();
        updateData.put("cutedBoardNumber", optimizeBoardModelDO.getCutedBoardNumber());
        updateData.put("cutedBlockNumber", optimizeBoardModelDO.getCutedBlockNumber());
        updateData.put("remainBoardInfo", toJsonString(remainBoardInfos));
        updateData.put(FIELD_UPDATETIME, simpleDateFormat.format(date));

        try {
            esDocumentService.updateById(ORDER_OPTIMIZE_PLATE_MODEL.getIndex(), optimizeBoardModelDO.getId(), HashMap.class, updateData);

        } catch (IOException | ElasticsearchException e) {
            log.error("Failed to sync with Elasticsearch: {}", e.getMessage());
            throw new ServiceException(ES_DATA_ERROR);
        }
    }



    private void updatePlateData(List<Long> orderIds, List<String> planNo, Integer cutedType) {

        if(CollUtil.isEmpty(planNo)){
            return;
        }

        List<PlateDO> plateDOS = plateMapper.selectPlateByPlateNo(orderIds, planNo, getUserOrganId());
        AssertUtils.notEmpty(plateDOS, THIS_PLAN_PLATE_DATA_ERROR);

        if(OrderPlateCutStatusEnum.OPENING.getStatus().equals(cutedType)){

            plateDOS.forEach(plate -> {
                if( !plate.getIsCutted().equals(cutedType) && !plate.getIsCutted().equals(OrderPlateCutStatusEnum.OPENED.getStatus())){
                    plate.setIsCutted(cutedType);
                }
            });

        }else {
            plateDOS.forEach(plate -> {
                if(!plate.getIsCutted().equals(cutedType)){
                    plate.setIsCutted(cutedType);
                }

            });
        }
        plateMapper.updateBatch(plateDOS);
    }







    @Transactional(rollbackFor = Exception.class)
    public void updateOrderAndPlanByCutStatus(PlanDO planDO,Long goodsNum,Integer cutedType,List<Long> orderIds,Integer boardCount){


        orderMapper.update(new LambdaUpdateWrapper<OrderDO>()
                .eq(OrderDO::getOrganId, getUserOrganId())
                .eq(OrderDO::getDeleted,false)
                .in(OrderDO::getId,orderIds)
                .set(OrderDO::getStatus,OrderStatusEnum.IN_PRODUCTION.getStatus()));


        // 未开料
        if(OrderPlateCutStatusEnum.NOCUTTING.getStatus().equals(cutedType)){

            OptimizePlanServiceImpl o = (OptimizePlanServiceImpl) AopContext.currentProxy();
            o.updateOrderAndPlanByCutStatusIsNoCutting(planDO,goodsNum,orderIds);

            return;

        }



        // 开料中
        if(OrderPlateCutStatusEnum.OPENING.getStatus().equals(cutedType) && !planDO.getStatus().equals(OrderPlateCutStatusEnum.OPENED.getStatus())){

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

            List<OrderDO> orderDOS = orderMapper.selectBatchIds(orderIds);
            AssertUtils.notEmpty(orderDOS,ORDER_PLAN_ERROR);

            orderDOS.forEach(f->f.setStatus(OrderStatusEnum.IN_PRODUCTION.getStatus()));

            planMapper.updatePlanStatus(planDO.getId(),planDO.getStatus(),getUserOrganId());

            orderMapper.updateBatch(orderDOS);
            return;

        }


        // 已开料
        if(OrderPlateCutStatusEnum.OPENED.getStatus().equals(cutedType)){

            planDO.setStatus(PlanStatusEnum.OPENING.getStatus());

            List<PlateDO> plateDOList = plateMapper.selectNoCutPlateByPlateId(orderIds, getUserOrganId());

            // 生产单的小板已全部开料
            List<Long> orderIdList = plateDOList.stream().map(PlateDO::getOrderId).distinct().toList();

            orderIds.removeAll(orderIdList);

            if(CollUtil.isNotEmpty(orderIds)){

                // 查询生产单的打包状态是否为已打包
                List<OrderDO> orderDOS = orderMapper.selectOrderPackList(orderIds, getUserOrganId());
                if (CollUtil.isNotEmpty(orderDOS)) {
                    orderDOS.forEach(f -> {
                        f.setStatus(OrderStatusEnum.FINISH_PRODUCTION.getStatus());
                        f.setFinishTime(LocalDateTime.now());
                    });

                    orderMapper.updateBatch(orderDOS);
                }
            }

            if(goodsNum.equals(Long.valueOf(boardCount))) {

                planDO.setStatus(PlanStatusEnum.OPENED.getStatus());
                planDO.setProduceTime(LocalDateTime.now());

            }

            planMapper.updateById(planDO);

        }

    }



    @Transactional(rollbackFor = Exception.class)
    public void updateOrderAndPlanByCutStatusIsNoCutting(PlanDO planDO,Long goodsNum,List<Long> orderIds){

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

        if(goodsNum.equals(0L)){

            Long plateId = planItemMapper.selectIsCuttingPlate(planDO.getId(), getUserOrganId());

            if(plateId == null){
                planDO.setStatus(PlanStatusEnum.NOCUTTING.getStatus());
            }

        }

        planMapper.updatePlanStatus(planDO.getId(),planDO.getStatus(),getUserOrganId());


        List<PlateDO> plateDOS = plateMapper.selectPlateIsCuttingStatus(orderIds, getUserOrganId());
        List<Long> orderIdList = plateDOS.stream().map(PlateDO::getOrderId).distinct().toList();

        orderIds.removeAll(orderIdList);

        if(CollUtil.isNotEmpty(orderIds)){

            List<OrderDO> orderDOS = orderMapper.selectBatchIds(orderIds);
            AssertUtils.notEmpty(orderDOS,ORDER_PLAN_ERROR);

            orderDOS.forEach(f->f.setStatus(OrderStatusEnum.SORTED.getStatus()));
            orderMapper.updateBatch(orderDOS);
        }
    }



    // 查询排单对应的加工方案组的配置信息并保存
    private void getPlanProcessSchemeConfig(Long processId,Long planId){

        List<ProcessSchemeDTO> schemeDTOS = systemConfigApi.getProcessSchemeConfig(processId).getCheckedData();

        List<ProcessSchemeModelDO> modelDOS = BeanUtils.toBean(schemeDTOS, ProcessSchemeModelDO.class);

        modelDOS.forEach(f->{
            f.setProcessId(processId);
            f.setPlanId(planId);
        });

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

        // 保存新的排单对应的加工方案组的配置信息
        esUtils.saveEsDocument(PLAN_PROCESS_SCHEME_CONFIG.getIndex(),modelDOS);

    }




    private void validatePlanStatus(PlanDO planDO,List<Boolean> isLocks){

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

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

        Boolean data = permissionApi.hasAnyPermissions(loginUser.getId(), "placeorder:list").getCheckedData();

        if(Boolean.TRUE.equals(planDO.getIsScheduled()) && isLocks.contains(true) && Boolean.FALSE.equals(data)){
            throw exception(THIS_PLAN_PROCESS_SCHEME_IS_LOCK);
        }

    }

    /**
     * 拆分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;
    }


}
