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

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
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.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.date.LocalDateTimeUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
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.vo.OrderPlanLockReqVO;
import com.cf.imes.module.executor.controller.admin.plan.bo.OrderRoomBodyList;
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.controller.admin.plate.vo.OrderViewPlatePageRespVO;
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.OrderGoodsPlateNumAndAreaDO;
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.orderGroup.OrderGroupDO;
import com.cf.imes.module.executor.dal.dataobject.orderItem.OrderItemDO;
import com.cf.imes.module.executor.dal.dataobject.orderItem.OrdersViewPlatesPageDO;
import com.cf.imes.module.executor.dal.dataobject.ordercomponent.OrderComponentDO;
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.dataobject.plate.PlateGoodDO;
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.orderGroup.OrderGroupMapper;
import com.cf.imes.module.executor.dal.mysql.orderItem.OrderItemMapper;
import com.cf.imes.module.executor.dal.mysql.ordercomponent.OrderComponentMapper;
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.enums.OrderPlanQueryTypeEnum;
import com.cf.imes.module.executor.enums.OrderStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
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.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static com.cf.imes.framework.common.util.json.JsonUtils.parseArray;
import static com.cf.imes.framework.redis.constants.RedisKeyConstants.ORDER_PLAN_OPTIMIZE_KEY;
import static com.cf.imes.framework.security.core.util.SecurityFrameworkUtils.getUserOrganId;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.*;

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

    @Resource
    private PlanMapper planMapper;

    @Resource
    private GoodsMapper goodsMapper;

    @Resource
    private PlateGoodMapper plateGoodMapper;

    @Resource
    private PlanItemMapper planItemMapper;

    @Resource
    private OrderItemMapper orderItemMapper;

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private PlateMapper plateMapper;

    @Resource
    private OrderBodyMapper orderBodyMapper;

    @Resource
    private RemainPlateMapper remainPlateMapper;

    @Resource
    private IdentifierGenerator identifierGenerator;

    @Resource
    private OrderGroupMapper orderGroupMapper;

    @Resource
    private OrderComponentMapper orderComponentMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 重复使用的常量
    private static final String FIELD_ORDER_ID = "o.id";
    private static final String FIELD_ORDER_TYPE = "o.order_type";
    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;

        Long processId = createReqVO.getProcessId();
        String processName = createReqVO.getProcessName();

        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, processName, 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, processName, loginUser, PlanTypeEnum.ORDERPLAN.getType());
        }
        // 计算排单的数量和面积
        if (CollUtil.isNotEmpty(planDOS)) {
            List<Long> planIdList = planDOS.stream().map(PlanDO::getId).toList();
            Map<Long, OrderPlanPlateNumAndAreaRespVO> planPlateNumAndAreaMap = planItemMapper.selectPlanPlateNumAndAreaGroup(planIdList)
                    .stream().collect(Collectors.toMap(OrderPlanPlateNumAndAreaRespVO::getPlanId, Function.identity()));
            for (PlanDO planDO : planDOS) {
                OrderPlanPlateNumAndAreaRespVO vo = planPlateNumAndAreaMap.get(planDO.getId());
                if (vo != null) {
                    planDO.setPlateNum(vo.getNum());
                    planDO.setPlateArea(vo.getArea());
                }
            }
            // 增加排单
            planMapper.insertBatch(planDOS);
        }
    }




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

        Long planId = updateReqVO.getId();

        if (planId != null) {

            PlanDO planDO = validatePlan(planId);
            Integer planDOStatus = planDO.getStatus();

            Long currProcessId = updateReqVO.getProcessId();
            if (currProcessId != null) {

                if (planDOStatus.equals(PlanStatusEnum.OPENING.getStatus()) || planDOStatus.equals(PlanStatusEnum.OPENED.getStatus())) {
                    throw new ServiceException(PLAN_PLATE_IS_CUT);
                }
                // 如果方案组id发生改变需要记录前序的
                Long processId = planDO.getProcessId();
                if (ObjectUtil.notEqual(processId, currProcessId)) {
                    planDO.setPreviousProcessId(processId);
                }

                planDO.setProcessId(currProcessId);
                planDO.setProcessName(updateReqVO.getProcessName());

                planMapper.updateById(planDO);

            } else {
                // true投入生产、false撤回生产
                Boolean isScheduled = updateReqVO.getIsScheduled();
                // 任何开料状态都不允许撤回了
                if (isScheduled != null
                        && Boolean.FALSE.equals(isScheduled)
                        && PlanStatusEnum.OPENING.getStatus().equals(planDOStatus) || PlanStatusEnum.OPENED.getStatus().equals(planDOStatus)) {
                    throw new ServiceException(PLAN_PLATE_IS_CUT);
                }

                planDO.setIsScheduled(isScheduled);
                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 new ServiceException(PLAN_NOT_ALLOW_DELETE, String.valueOf(f.getId()));
            }
        });

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

        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.deleteByIds(plateIdList);

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

        }
    }

    /**
     * 重置板件排单和优化属性 & 计算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 new ServiceException(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) {
        Long organId = getUserOrganId();

        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", organId)
                    .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());

            // 入参有订单号跟客户的时候，从订单表查，然后查订单下板件的排单号，然后做排单id去做分页
            if (StringUtils.isNotEmpty(pageReqVO.getOrderNos()) || StringUtils.isNotEmpty(pageReqVO.getCustomer())) {
                LambdaQueryWrapperX<OrderDO> orderQuery = new LambdaQueryWrapperX<OrderDO>()
                        .eq(OrderDO::getOrganId, organId)
                        .eq(OrderDO::getDeleted, false)
                        .eqIfPresent(OrderDO::getId, pageReqVO.getOrderNos())
                        .likeIfPresent(OrderDO::getCustomer, pageReqVO.getCustomer())
                        .selectX(OrderDO::getId);

                List<Long> orderIds = orderMapper.selectList(orderQuery)
                        .stream().map(OrderDO::getId).toList();

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

                List<Long> planIds = plateMapper.selectPlanIdsByOrderCondition(orderIds, pageReqVO.getCustomer(), organId);

                if (CollUtil.isEmpty(planIds)) {
                    return new PageResult<>();
                }
                queryWrapperX.in(FIELD_OP_ID, planIds);
            }

            // 分页查询
            planDOPageResult = planMapper.selectPlanPageList(page,queryWrapperX);

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

        }


        List<PlanDO> records = planDOPageResult.getRecords();

        // planId:goodsDO的对应关系
        Map<Long, PlateGoodDO> planGoodsMap = new HashMap<>();

        List<PlanDO> mixedPlans = records.stream()
                .filter(p -> ObjectUtil.equals(PlanTypeEnum.MIXEDORDERPLAN.getType(), p.getType()))
                .toList();

        // 查询混单实际使用板材的信息
        if (CollUtil.isNotEmpty(mixedPlans)) {

            List<String> goodsIds = mixedPlans.stream()
                    .map(PlanDO::getGoodsId)
                    .distinct()
                    .toList();

            // 查询需要的板材信息
            List<PlateGoodDO> goodsDOS = plateGoodMapper.selectGoodsListByGoodsId(goodsIds);

            Map<String, PlateGoodDO> goodsMap = goodsDOS.stream()
                    .collect(Collectors.toMap(
                            PlateGoodDO::getGoodsId,
                            Function.identity()
                    ));

            for (PlanDO plan : mixedPlans) {
                planGoodsMap.put(plan.getId(), goodsMap.get(plan.getGoodsId()));
            }
        }

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

        // 整理出所有排单涉及的订单
        Set<Long> orderIdSet = records.stream()
                .map(PlanDO::getOrderNos)
                .filter(StringUtils::isNotBlank)
                .flatMap(orderNos -> JSON.parseArray(orderNos)
                        .toJavaList(Long.class)
                        .stream())
                .collect(Collectors.toCollection(LinkedHashSet::new));


        // 查询排单对应的大板板材信息
        List<PlateInfoVO> plateInfoVOS = planItemMapper.selectOrderPlanGoodsList(planIds, orderIdSet, organId);

        // 获取排单涉及的所有订单信息
        Map<Long, OrderDO> orderMap = CollUtil.isEmpty(orderIdSet)
                ? Collections.emptyMap()
                : orderMapper.selectByIds(orderIdSet).stream()
                .collect(Collectors.toMap(OrderDO::getId, Function.identity()));


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


        planRespVOPageResult.forEach(e -> {
            // 拆单大板信息列表
            e.setPlateInfoList(getPlateInfoList(e.getId(), plateInfoVOS));
            // 混单实际选择大板信息列表
            getMixedPlanGoodsInfo(e, planGoodsMap);
            // 未优化小板数量
            e.setUnOptimizePlateCount(e.getPlateNum() - e.getOptimizedPlateNum());
            // 订单信息
            e.setOrderList(getOrderList(e.getOrderNos(), orderMap));
        });


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

    @Override
    public List<PlanRespVO> getPlanSortPage() {
        List<PlanDO> plans = planMapper.selectPlanSortList();
        if (CollUtil.isEmpty(plans)) {
            return List.of();
        }

        List<Long> planIds = plans.stream()
                .map(PlanDO::getId)
                .toList();
        Set<Long> orderIds = plans.stream()
                .map(PlanDO::getOrderNos)
                .filter(StringUtils::isNotBlank)
                .flatMap(orderNos -> JSON.parseArray(orderNos)
                        .toJavaList(Long.class)
                        .stream())
                .collect(Collectors.toCollection(LinkedHashSet::new));

        // 与已排订单分页保持一致，一次查询全部排单的大板信息，避免逐排单查询产生N+1。
        List<PlateInfoVO> plateInfoList = CollUtil.isEmpty(orderIds)
                ? List.of()
                : planItemMapper.selectOrderPlanGoodsList(planIds, orderIds, getUserOrganId());

        // 混单的大板来自实际选择板材，按商品编号批量加载后再映射回排单。
        Map<Long, PlateGoodDO> planGoodsMap = new HashMap<>();
        List<PlanDO> mixedPlans = plans.stream()
                .filter(plan -> ObjectUtil.equals(
                        PlanTypeEnum.MIXEDORDERPLAN.getType(), plan.getType()))
                .toList();
        if (CollUtil.isNotEmpty(mixedPlans)) {
            List<PlateGoodDO> goodsList = plateGoodMapper.selectGoodsListByGoodsId(
                    mixedPlans.stream()
                            .map(PlanDO::getGoodsId)
                            .distinct()
                            .toList());
            Map<String, PlateGoodDO> goodsMap = goodsList.stream()
                    .collect(Collectors.toMap(PlateGoodDO::getGoodsId, Function.identity()));
            mixedPlans.forEach(plan -> planGoodsMap.put(
                    plan.getId(), goodsMap.get(plan.getGoodsId())));
        }

        List<PlanRespVO> result = BeanUtil.copyToList(plans, PlanRespVO.class);
        result.forEach(plan -> {
            plan.setPlateInfoList(getPlateInfoList(plan.getId(), plateInfoList));
            getMixedPlanGoodsInfo(plan, planGoodsMap);
        });
        return result;
    }


    @Override
    public PageResult<OrderRespVOCopy> getOrderPage(OrderPageReqVOCopy pageReqVO) {
        Long organId = getUserOrganId();

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

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

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

        List<Long> orderGoodsIds = new ArrayList<>();
        // 查询类型：订单分组
        int type = OrderPlanQueryTypeEnum.ORDER_GROUP.getType();
        if(CollUtil.isNotEmpty(goodsIds)) {
            List<Long> orderIdList = Optional.ofNullable(pageReqVO.getOrderIds()).orElse(new ArrayList<>());

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

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

                .inIfPresent(FIELD_ORDER_ID, pageReqVO.getOrderIds())
                .likeIfPresent(FIELD_ORDER_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, LocalDateTimeUtils.generateTimeSection(pageReqVO.getCreateTime()))
                .eqIfPresent(FIELD_ORDER_TYPE, pageReqVO.getOrderType());

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

        // 查询满足未排单条件的顶级订单id
        IPage<Long> rootOrderIdList = planItemMapper.selectRootOrderIdList(page, queryWrapperX);
        long rootOrderIdListTotal = rootOrderIdList.getTotal();
        List<Long> records = rootOrderIdList.getRecords();
        if (rootOrderIdListTotal == 0 || CollUtil.isEmpty(records)) {
            return new PageResult<>();
        }

        // 查询顶级列表的信息，分页在顶级查询做，此处查询全部信息避免分页互相影响
        PageDTO<OrderRespVOCopy> secondPage = new PageDTO<>(pageReqVO.getPageNo(), PAGE_SIZE_NONE);
        IPage<OrderRespVOCopy> orderList = planItemMapper.selectOrderList(secondPage, queryWrapperX, type, orderGoodsIds, records);

        List<OrderRespVOCopy> mainOrderList = orderList.getRecords();

        // 订单分组查询子单
        if (CollUtil.isNotEmpty(mainOrderList)) {

            // 主单id列表
            Long[] mainOrderIds = mainOrderList.stream().map(OrderRespVOCopy::getOrderId).toArray(Long[]::new);
            List<OrderRespVOCopy> childList = planItemMapper.selectOrderChildList(queryWrapperX, type, orderGoodsIds, mainOrderIds);

            if (CollUtil.isNotEmpty(childList)) {
                // 按 parentId 分组
                Map<Long, List<OrderRespVOCopy>> childMap = childList.stream()
                        .collect(Collectors.groupingBy(OrderRespVOCopy::getParentId));

                // 回填 child
                for (OrderRespVOCopy order : mainOrderList) {
                    order.setChild(childMap.getOrDefault(order.getOrderId(), null));
                }
            }
            return new PageResult<>(mainOrderList, rootOrderIdListTotal);
        }

        return new PageResult<>(mainOrderList, rootOrderIdListTotal);

    }





    @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;

        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(FIELD_ORDER_ID, pageReqVO.getOrderIds())
                    .likeIfPresent(FIELD_ORDER_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())
                    .likeIfPresent("og.goods_name", pageReqVO.getGoodsName())
                    .likeIfPresent("og.material", pageReqVO.getMaterial())
                    .likeIfPresent("og.color", pageReqVO.getColor())
                    .eqIfPresent("og.thickness", pageReqVO.getThickness());

            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<>(records, orderGoodsResps.getTotal());

    }




    @Override
    public PageResult<OrderViewPlatePageRespVO> getPlateByPlanId(GetPlateByPlanIdVO vo) {
        List<Long> planIds = vo.getPlanIds();
        if (CollUtil.isEmpty(planIds)) {
            return null;
        }

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

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

        // todo 当前默认单排单查询
        Set<Long> orderIdSet = planDOS.stream()
                .map(PlanDO::getOrderNos)
                .filter(StringUtils::isNotBlank)
                .flatMap(orderNos -> JSON.parseArray(orderNos)
                        .toJavaList(Long.class)
                        .stream())
                .collect(Collectors.toCollection(LinkedHashSet::new));


        // 分页查询排单板件
        PageDTO<OrdersViewPlatesPageDO> page = new PageDTO<>(vo.getPageNo(), vo.getPageSize());
        IPage<OrdersViewPlatesPageDO> ordersViewPlatesPageDOIPage = plateMapper.selectPlanPlateInfoPage(page, planIds, orderIdSet);

        long total = ordersViewPlatesPageDOIPage.getTotal();
        if (total == 0) {
            return new PageResult<>();
        }

        List<OrdersViewPlatesPageDO> ordersViewPlatesPageDOIPageRecords = ordersViewPlatesPageDOIPage.getRecords();
        List<Long> plateIds = ordersViewPlatesPageDOIPageRecords.stream().map(OrdersViewPlatesPageDO::getId).toList();

        if (CollUtil.isEmpty(plateIds)) {
            return new PageResult<>();
        }
        // 查询关联关系
        List<OrderItemDO> orderItemRelation = orderItemMapper.selectList(new LambdaQueryWrapperX<OrderItemDO>()
                .in(OrderItemDO::getOrderId, orderIdSet)
                .in(OrderItemDO::getPlateId, plateIds)
                .select(OrderItemDO::getPlateId, OrderItemDO::getRoomId, OrderItemDO::getBodyId, OrderItemDO::getGroupId, OrderItemDO::getCompId)
        );

        // 暂存plateId:set<groupId>
        Map<Long, Set<Long>> plateGroupMap = new HashMap<>();
        // 暂存plateId:set<compId>
        Map<Long, Set<Long>> plateCompMap = new HashMap<>();
        // 暂存plateId:bodyId
        Map<Long, Long> plateBodyMap = new HashMap<>();

        Map<Long, OrdersViewPlatesPageDO> plateMap =
                ordersViewPlatesPageDOIPageRecords.stream()
                        .collect(Collectors.toMap(
                                OrdersViewPlatesPageDO::getId,
                                Function.identity(),
                                (a, b) -> a
                        ));

        for (OrderItemDO item : orderItemRelation) {

            Long plateId = item.getPlateId();

            // 累计plateId下group_id
            if (item.getGroupId() != null) {
                plateGroupMap
                        .computeIfAbsent(plateId, k -> new HashSet<>())
                        .add(item.getGroupId());
            }

            // 累计plateId下comp_id
            if (item.getCompId() != null) {
                plateCompMap
                        .computeIfAbsent(plateId, k -> new HashSet<>())
                        .add(item.getCompId());
            }

            // 记录plateId下body_id
            if (item.getBodyId() != null) {
                plateBodyMap
                        .computeIfAbsent(plateId, k -> item.getBodyId());
            }
        }

        // 查询加工组
        Set<Long> allGroupIds = plateGroupMap.values()
                .stream()
                .flatMap(Set::stream)
                .collect(Collectors.toSet());

        Map<Long, String> groupNameMap = Collections.emptyMap();

        if (!allGroupIds.isEmpty()) {
            groupNameMap = orderGroupMapper.selectByIds(allGroupIds)
                    .stream()
                    .collect(Collectors.toMap(
                            OrderGroupDO::getId,
                            OrderGroupDO::getName,
                            (a, b) -> a
                    ));
        }

        // 查询部件
        Set<Long> allCompIds = plateCompMap.values()
                .stream()
                .flatMap(Set::stream)
                .collect(Collectors.toSet());

        Map<Long, String> compNameMap = Collections.emptyMap();

        if (!allCompIds.isEmpty()) {
            compNameMap = orderComponentMapper.selectByIds(allCompIds)
                    .stream()
                    .collect(Collectors.toMap(
                            OrderComponentDO::getId,
                            OrderComponentDO::getName,
                            (a, b) -> a
                    ));
        }

        // 查询房间
        Set<Long> allBodyIds = plateBodyMap.values()
                .stream()
                .collect(Collectors.toSet());

        Map<Long, OrderBodyDO> bodyMap = Collections.emptyMap();

        if (!allBodyIds.isEmpty()) {
            bodyMap = orderBodyMapper.selectByIds(allBodyIds)
                    .stream()
                    .collect(Collectors.toMap(
                            OrderBodyDO::getId,
                            Function.identity(),
                            (a, b) -> a
                    ));
        }

        List<OrderViewPlatePageRespVO> records = new ArrayList<>();
        for (Map.Entry<Long, OrdersViewPlatesPageDO> entry : plateMap.entrySet()) {

            Long plateId = entry.getKey();
            OrdersViewPlatesPageDO plate = entry.getValue();

            OrderViewPlatePageRespVO plateRespVO = BeanUtil.copyProperties(plate, OrderViewPlatePageRespVO.class);

            // 用逗号拼接加工组名称
            Set<Long> plateGroupIds = plateGroupMap.getOrDefault(plateId, Collections.emptySet());
            List<String> groupNames = plateGroupIds.stream()
                    .map(groupNameMap::get)
                    .filter(Objects::nonNull)
                    .toList();

            plateRespVO.setProcessGroupName(String.join(",", groupNames));

            // 用逗号拼接部件组名称
            Set<Long> plateCompIds = plateCompMap.getOrDefault(plateId, Collections.emptySet());
            List<String> compNames = plateCompIds.stream()
                    .map(compNameMap::get)
                    .filter(Objects::nonNull)
                    .toList();

            plateRespVO.setComponentName(String.join(",", compNames));

            // 匹配房间名、柜体名
            Long bodyId = plateBodyMap.get(plateId);
            OrderBodyDO orderBodyDO = bodyMap.get(bodyId);
            if(ObjectUtil.isNotNull(orderBodyDO)) {
                plateRespVO.setBodyName(orderBodyDO.getName());
                plateRespVO.setRoomName(orderBodyDO.getRoomName());
            }

            records.add(plateRespVO);
        }
        return new PageResult<>(records, total);

    }





    @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();

        for (Long planId : planIds) {

            PlanDO planDO = planMapper.selectById(planId);

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

        }

        // 500条更新一次排单顺序
        List<List<SortVO>> batches = ListUtil.partition(sortVos, 500);

        for (List<SortVO> batch : batches) {
            planMapper.batchUpdatePlanSort(batch);
        }
    }



    @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 = new ArrayList<>();

        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 new ServiceException(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 new ServiceException(PLAN_NOT_DATA_EXISTS, planId);
            }
            planDOS.add(planDO);
        }

        return planDOS;
    }

    /**
     * 设置混单的实际排单选择大板信息
     *
     * @param planRespVO
     * @param planGoodsMap
     */
    private void getMixedPlanGoodsInfo(PlanRespVO planRespVO, Map<Long, PlateGoodDO> planGoodsMap) {
        if (ObjectUtil.equals(planRespVO.getType(), PlanTypeEnum.MIXEDORDERPLAN.getType())) {
            PlateInfoVO actualPlateInfoVO = new PlateInfoVO();
            PlateGoodDO goodsDO = planGoodsMap.get(planRespVO.getId());
            if (ObjectUtil.isNotNull(goodsDO)) {
                actualPlateInfoVO.setMaterial(goodsDO.getMaterial());
                actualPlateInfoVO.setColor(goodsDO.getColor());
                actualPlateInfoVO.setThickness(BigDecimal.valueOf(goodsDO.getThickness()));
                planRespVO.setActualPlanGoodsInfo(actualPlateInfoVO);
            }
        }
    }

    // 板材信息列表
    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(String orderNos, Map<Long, OrderDO> orderMap) {
        List<PlanOrderRespVO> orderList = JSON.parseArray(orderNos).toJavaList(Long.class).stream()
                .map(orderMap::get)
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(OrderDO::getId).reversed())
                .map(order -> {
                    PlanOrderRespVO vo = BeanUtils.toBean(order, PlanOrderRespVO.class);
                    vo.setSalesman(order.getSalesman());
                    return vo;
                })
                .collect(Collectors.toList());
        return orderList;
    }



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

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

        AssertUtils.notEmpty(planDO, ORDER_PLAN_DATA_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();

        validatePlanExists(deletePlanIds);

        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.selectByIds(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<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.physicalDeleteByIds(deleteEmptyPlanIdList, getUserOrganId());
        }

        // 更新排单下的订单列表
        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));
            }
        }
    }


    @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);
        }

        // 已开料排单修改为开料中
        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);

    }

    /**
     * 新增排单板件 & 重新计算订单和材质下的数量和面积
     * @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_PART_PLANNED);
                }

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

        OrderPlanPlateNumAndAreaRespVO planPlateNumAndAreaRespVO = planItemMapper.selectPlanPlateNumAndArea(planId);
        // 更新排单板件数量和面积
        planMapper.update(new LambdaUpdateWrapper<PlanDO>()
                .eq(PlanDO::getId, planId)
                .set(PlanDO::getPlateNum, planPlateNumAndAreaRespVO.getNum())
                .set(PlanDO::getPlateArea, planPlateNumAndAreaRespVO.getArea()));

        // 统计更新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_PART_PLANNED);
                    }
                }

                int version = goodsDO.getVersion();
                // 统计每个order_goods下的未排单面积，更新到order_goods中
                OrderPlanPlateNumAndAreaRespVO goodsPlateNumAndAreaPlanInfo = goodsMapper.selectPlannedPlateNumAndArea(orderGoodsId);
                BigDecimal goodsPlateNumAndAreaPlanInfoArea = goodsPlateNumAndAreaPlanInfo.getArea();
                Integer goodsPlateNumAndAreaPlanInfoNum = goodsPlateNumAndAreaPlanInfo.getNum();
                int update = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                        .eq(GoodsDO::getId, orderGoodsId)
                        .eq(GoodsDO::getVersion, version)
                        .set(GoodsDO::getPlannedPlateArea, goodsPlateNumAndAreaPlanInfoArea)
                        .set(GoodsDO::getPlannedPlateNum, goodsPlateNumAndAreaPlanInfoNum)
                        .set(GoodsDO::getVersion, version + 1));
                if (update == 0) {
                    throw new ServiceException(ErrorCodeConstants.GOODS_PLATE_UPDATE_CONCURRENCY_ERROR, orderGoodsId);
                }
            }
            OrderPlanPlateNumAndAreaRespVO planPlateNumAndAreaRespVO = planItemMapper.selectPlanPlateNumAndArea(planId);
            // 更新排单板件数量和面积
            planMapper.update(new LambdaUpdateWrapper<PlanDO>()
                    .eq(PlanDO::getId, planId)
                    .set(PlanDO::getPlateNum, planPlateNumAndAreaRespVO.getNum())
                    .set(PlanDO::getPlateArea, planPlateNumAndAreaRespVO.getArea()));
        }

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


    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;
    }


    /**
     * 混单情况下不用混单的材质按goodsCode商品编码分组
     *
     * @param items
     * @return
     */
    public static Map<String, List<Long>> groupByGoodsCodeEqualToOne(List<SavePlanPlateList.GoodsItemList> items) {
        Map<String, List<Long>> groupedItems = new HashMap<>();

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

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

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

        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,
                                             String processName,
                                             LoginUser loginUser,
                                             Integer mixedType){


        Map<String, List<Long>> grouped;

        if(CollUtil.isNotEmpty(planActualGoodsLists)) {

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


            // 不用混单的材质按goodsCode商品编码分组
            grouped = groupByGoodsCodeEqualToOne(goodsItemList);

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

        } else {
            // 不用混单的材质按goodsCode商品编码分组
            grouped = groupByGoodsCodeEqualToOne(goodsItemList);

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

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


    }

    /**
     * 订单列表匹配到商品上
     *
     * @param list1 商品列表
     * @param list2 带订单列表的商品分组
     */
    public void mergeOrderGoodsLists(List<OrderGoodsResp> list1, List<OrderGoodsResp> list2) {
        Map<String, OrderGoodsResp> list1Map = new HashMap<>(list1.size());

        long sort = 1;

        // list1构建key分组
        for (OrderGoodsResp item1 : list1) {
            item1.setSort(String.valueOf(sort++));
            list1Map.put(generateKey(item1), item1);
        }

        // list2按key分组
        Map<String, List<OrderGoodsResp>> list2Map =
                list2.stream().collect(Collectors.groupingBy(this::generateKey));

        // key匹配的情况下，把list2的orderIds加到list1中
        for (var entry : list2Map.entrySet()) {
            OrderGoodsResp target = list1Map.get(entry.getKey());

            if (target == null) continue;

            for (OrderGoodsResp item : entry.getValue()) {
                if (CollUtil.isNotEmpty(item.getOrderIds())) {
                    target.getOrderIds().addAll(item.getOrderIds());
                }
            }
        }
    }

    /**
     * 构建key
     *
     * @param item
     * @return
     */
    private 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,
                                                String processName,
                                                LoginUser loginUser)  {

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

        long sort = System.currentTimeMillis();

        for (PlanActualGoodsList f : planActualGoodsLists) {

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

            // 排单ID
            Long planId = (Long) identifierGenerator.nextId(null);

            // 更新小板排单id
            List<Long> plateIds = plateList.stream().map(SavePlanPlateList.PlateItemList::getPlateId).toList();
            if (CollUtil.isNotEmpty(plateIds)) {
                List<List<Long>> batchPlateIdList = batchProcess(plateIds);
                for (List<Long> batchIds : batchPlateIdList) {
                    int updatePlateNum = plateMapper.update(new LambdaUpdateWrapper<PlateDO>()
                            .in(PlateDO::getId, batchIds)
                            .eq(PlateDO::getPlanId, 0)
                            .set(PlateDO::getPlanId, planId));
                    if (updatePlateNum != batchIds.size()) {
                        throw new ServiceException(CREAT_PLAN_PLATE_PART_PLANNED);
                    }
                }
            }

            // 累计排单下的订单号 & 累计发生改变的订单号
            Set<Long> planOrderIdSet = plateList.stream()
                    .map(SavePlanPlateList.PlateItemList::getOrderId)
                    .collect(Collectors.toSet());
            orderIdSet.addAll(planOrderIdSet);

            // 累计涉及的商品id
            allGoodsIds.addAll(plateList.stream().map(SavePlanPlateList.PlateItemList::getId).toList());

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

            // 创建排单
            PlanDO planDO = PlanDO.builder()
                    .id(planId)
                    .sort(++sort)
                    .type(PlanTypeEnum.MIXEDORDERPLAN.getType())
                    .processId(processId)
                    .processName(processName)
                    .orderNos(planOrderIdSet.toString())
                    .produceTime(null)
                    .status(PlanStatusEnum.NOCUTTING.getStatus())
                    .operator(loginUser.getNickname())
                    .planTime(LocalDateTime.now())
                    .goodsId(goodsId)
                    .plateNum(plateList.size())
                    .build();

            planDOS.add(planDO);
        }
        // 统计更新order_goods的数量和面积
        computePlatePlanAndComputeOrderGoodsInfo(allGoodsIds);
        // 统计更新orders的小板数量和面积
        computeOrderInfo(orderIdSet);
    }




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

        long sort = System.currentTimeMillis();

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

        // 进行数据处理：value 里为 大板的分类ID
        for (Map.Entry<String, List<Long>> entry : grouped.entrySet()) {
            List<Long> value = entry.getValue();

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

            // 排单ID
            Long planId = (Long) identifierGenerator.nextId(null);

            // 直接小板排单id
            List<Long> plateIds = plateList.stream().map(SavePlanPlateList.PlateItemList::getPlateId).toList();
            if (CollUtil.isNotEmpty(plateIds)) {
                List<List<Long>> batchPlateIdList = batchProcess(plateIds);
                for (List<Long> batchIds : batchPlateIdList) {
                    int updatePlateNum = plateMapper.update(new LambdaUpdateWrapper<PlateDO>()
                            .in(PlateDO::getId, batchIds)
                            .eq(PlateDO::getPlanId, 0)
                            .set(PlateDO::getPlanId, planId));
                    if (updatePlateNum != batchIds.size()) {
                        throw new ServiceException(CREAT_PLAN_PLATE_PART_PLANNED);
                    }
                }
            }

            // 累计排单下的订单号 & 累计发生改变的订单号
            Set<Long> planOrderIdSet = plateList.stream()
                    .map(SavePlanPlateList.PlateItemList::getOrderId)
                    .collect(Collectors.toSet());
            orderIdSet.addAll(planOrderIdSet);

            // 累计涉及的商品id
            allGoodsIds.addAll(plateList.stream().map(SavePlanPlateList.PlateItemList::getId).toList());

            // 创建排单
            PlanDO planDO = PlanDO.builder()
                    .id(planId)
                    .sort(++sort)
                    .type(planType)
                    .processId(processId)
                    .processName(processName)
                    .orderNos(planOrderIdSet.toString())
                    .produceTime(null)
                    .status(PlanStatusEnum.NOCUTTING.getStatus())
                    .operator(loginUser.getNickname())
                    .planTime(LocalDateTime.now())
                    .plateNum(plateList.size())
                    .build();

            planDOS.add(planDO);
        }
        // 统计更新order_goods的数量和面积
        computePlatePlanAndComputeOrderGoodsInfo(allGoodsIds);
        // 统计更新orders的小板数量和面积
        computeOrderInfo(orderIdSet);
    }

    /**
     * 统计更新order_goods的小板数量和面积
     *
     * @param allGoodsIds
     */
    public void computePlatePlanAndComputeOrderGoodsInfo(Set<Long> allGoodsIds) {
        if (CollUtil.isNotEmpty(allGoodsIds)) {
            List<GoodsDO> goodsDOList = goodsMapper.selectByIds(new ArrayList<>(allGoodsIds));
            Map<Long, OrderGoodsPlateNumAndAreaDO> statsMap = goodsMapper.selectPlannedPlateNumAndAreaGroup(allGoodsIds)
                    .stream().collect(Collectors.toMap(OrderGoodsPlateNumAndAreaDO::getOrderGoodsId, Function.identity()));
            for (GoodsDO goodsDO : goodsDOList) {
                OrderGoodsPlateNumAndAreaDO stats = statsMap.getOrDefault(goodsDO.getId(),
                        OrderGoodsPlateNumAndAreaDO.builder().num(0).area(BigDecimal.ZERO).build());
                int version = goodsDO.getVersion();
                int update = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                        .eq(GoodsDO::getId, goodsDO.getId())
                        .eq(GoodsDO::getVersion, version)
                        .set(GoodsDO::getPlannedPlateArea, stats.getArea())
                        .set(GoodsDO::getPlannedPlateNum, stats.getNum())
                        .set(GoodsDO::getVersion, version + 1));
                if (update == 0) {
                    throw new ServiceException(ErrorCodeConstants.CREATE_PLAN_GOODS_UPDATE_CONCURRENCY_ERROR, goodsDO.getId());
                }
            }
        }
    }

    /**
     * 统计更新orders的小板数量和面积
     *
     * @param orderIdSet
     */
    public void computeOrderInfo(Set<Long> orderIdSet) {
        if (CollUtil.isEmpty(orderIdSet)) {
            return;
        }

        // 批量查询订单信息（获取version和status）
        Map<Long, OrderDO> orderMap = orderMapper.selectByIds(new ArrayList<>(orderIdSet)).stream()
                .collect(Collectors.toMap(OrderDO::getId, Function.identity()));

        // 批量统计已排板件数量和面积
        Map<Long, OrderPlanPlateNumAndAreaRespVO> statsMap = orderMapper.selectPlannedPlateNumAndAreaGroup(orderIdSet).stream()
                .collect(Collectors.toMap(OrderPlanPlateNumAndAreaRespVO::getOrderId, Function.identity()));

        // 循环更新每个订单（需要version乐观锁校验 + 条件状态更新）
        for (Long orderId : orderIdSet) {
            OrderDO orderDO = orderMap.get(orderId);
            OrderPlanPlateNumAndAreaRespVO stats = statsMap.getOrDefault(orderId,
                    OrderPlanPlateNumAndAreaRespVO.builder().num(0).area(BigDecimal.ZERO).build());

            Integer num = stats.getNum();
            int version = orderDO.getVersion();

            LambdaUpdateWrapper<OrderDO> updateWrapper = new LambdaUpdateWrapper<OrderDO>()
                    .eq(OrderDO::getId, orderId)
                    .eq(OrderDO::getVersion, version)
                    .set(OrderDO::getPlannedPlateArea, stats.getArea())
                    .set(OrderDO::getPlannedPlateNum, num)
                    .set(OrderDO::getVersion, version + 1);

            Integer orderDOStatus = orderDO.getStatus();
            // 更新订单排单状态
            if (ObjectUtil.equals(orderDOStatus, OrderStatusEnum.NEW_ORDER.getStatus())) {
                // 如果订单下已经没有排单板件，更新订单为新单
                if (num > 0) {
                    updateWrapper.set(OrderDO::getStatus, OrderStatusEnum.SORTED.getStatus());
                }
            }
            // 如果订单下已经没有排单板件，更新订单为新单
            if (ObjectUtil.equals(orderDOStatus, OrderStatusEnum.SORTED.getStatus())) {
                if (num == 0) {
                    updateWrapper.set(OrderDO::getStatus, OrderStatusEnum.NEW_ORDER.getStatus());
                }
            }

            int update = orderMapper.update(updateWrapper);
            if (update == 0) {
                throw new ServiceException(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;
    }

    @Override
    public void getPlanLock(OrderPlanLockReqVO orderPlanLockReqVO) {
        Long planId = orderPlanLockReqVO.getPlanId();
        String lockValInput = orderPlanLockReqVO.getVal();

        PlanDO planDO = planMapper.selectById(planId);

        AssertUtils.notEmpty(planDO, PLAN_NOT_EXISTS);

        String planLockKey = String.format(ORDER_PLAN_OPTIMIZE_KEY, planId);

        // 获取优化锁
        String lockVal = stringRedisTemplate.opsForValue().get(planLockKey);

        // 没有锁，尝试获取
        if (StrUtil.isBlank(lockVal)) {

            Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
                    planLockKey,
                    lockValInput,
                    Duration.ofMinutes(30)
            );

            if (Boolean.TRUE.equals(success)) {
                return;
            }

            // 极端并发场景
            lockVal = stringRedisTemplate.opsForValue().get(planLockKey);
        }

        // 用不同的lockKey去请求优化提示已锁单
        if (!StrUtil.equals(lockVal, lockValInput)) {
            throw exception(ORDER_PLAN_LOCKED);
        }
    }

    @Override
    public void releasePlanLock(OrderPlanLockReqVO orderPlanLockReqVO) {
        Long planId = orderPlanLockReqVO.getPlanId();
        String lockValInput = orderPlanLockReqVO.getVal();

        String lockKey = String.format(ORDER_PLAN_OPTIMIZE_KEY, planId);

        String lockVal = stringRedisTemplate.opsForValue().get(lockKey);

        if (Objects.equals(lockVal, lockValInput)) {
            stringRedisTemplate.delete(lockKey);
        }
    }
}
