package com.shkj.wms.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.executor.RedissonLockExecutor;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shkj.common.constant.RedisConstant;
import com.shkj.common.core.domain.Result;
import com.shkj.common.core.redis.RedisCache;
import com.shkj.common.utils.DateUtils;
import com.shkj.common.utils.SecurityUtils;
import com.shkj.common.utils.StringUtils;
import com.shkj.common.utils.bean.BeanUtils;
import com.shkj.system.service.ISysParameterService;
import com.shkj.wms.bo.*;
import com.shkj.wms.constants.ParameterConstants;
import com.shkj.wms.constants.RedisLockConstants;
import com.shkj.wms.domain.*;
import com.shkj.wms.enums.*;
import com.shkj.wms.mapper.BusinOutInPlanMapper;
import com.shkj.wms.service.*;
import com.shkj.wms.utils.BeanMapperUtils;
import com.shkj.wms.utils.IntIdUtil;
import com.shkj.wms.utils.JsonUtil;
import com.shkj.wms.utils.WmsCallWcsUtil;
import com.shkj.wms.vo.*;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.function.FailableShortSupplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 出入库计划Service业务层处理
 *
 * @author lch
 * @date 2023-06-02
 */
@Slf4j
@Service
public class BusinOutInPlanServiceImpl extends ServiceImpl<BusinOutInPlanMapper, BusinOutInPlan> implements IBusinOutInPlanService {

    @Autowired
    private IBaseContainerService baseContainerService;

    @Autowired
    private IBaseLocationService baseLocationService;

    @Autowired
    private IBusinStockMoveDService businStockMoveDService;

    @Autowired
    private IBaseMoldService baseMoldService;

    @Autowired
    private ISysThirdExceptionRequestService thirdExceptionRequestService;

    @Autowired
    private WmsCallWcsUtil wmsCallWcsUtil;

    @Autowired
    private IBusinStockInvService iBusinStockInvService;

    @Autowired
    private ISysStockDetailService sysStockDetailService;

    @Autowired
    private ISysThirdExceptionRequestService sysThirdExceptionRequestService;


    @Autowired
    public LockTemplate lockTemplate;


    @Autowired
    private RedisCache redisCache;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private BusinOutInPlanMapper businOutInPlanMapper;

    @Autowired
    private ISysStockDetailQrcodeService iSysStockDetailQrcodeService;

    @Autowired
    private ISysParameterService isysParameterService;


    @Override
    public List<BusinOutInPlanVo> queryList(BusinOutInPlanQueryBo bo) {
        List<BusinOutInPlanVo> voList = this.baseMapper.queryList(bo);
        return voList;

    }


    /**
     * 加入入库计划
     *
     * @param bo 薄
     * @return {@code Result<Boolean>}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> addInPlan(BusinInPlanAddBo bo) {
        final LockInfo lockInfo = lockTemplate.lock("addInPlan", RedisLockConstants.expire, RedisLockConstants.acquireTimeout, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("入库业务处理中,请稍后再试");
        }
        try {
            log.info("请求入库的参数{}",bo);
            String containerCode = bo.getContainerCode();
            //判断添加的托盘是否有未完成的入库记录
            BusinOutInPlan businOutInPlan = this.getOrderByContainerCode(containerCode, PlanTypeEnum.IN.getValue());
            if (businOutInPlan!=null){
                return Result.err().msg("此托盘存在相关未完成入库任务单据:"+businOutInPlan.getOrderNo());
            }

            BusinOutInPlan businOutInPlanOut = this.getOrderByContainerCode(containerCode, PlanTypeEnum.OUT.getValue());
            if (businOutInPlanOut!=null){
                return Result.err().msg("此托盘存在相关未完成的出库任务单据:"+businOutInPlan.getOrderNo());
            }
            BaseContainer container = baseContainerService.queryContainerByCode(containerCode);
            if (container == null) {
                return Result.err().msg("该托盘不存在");
            }
            String containerType = container.getContainerType();
            String moldType = bo.getMoldType();

            if (!containerType.equals(moldType)){
                return Result.err().msg("模具类型同托盘类型不匹配");
            }
            //模具类型是高模具，不能放到托盘类型是低托盘类型的托盘上
            /*if (moldType.equals("3") && containerType.equals("6")){
                return Result.err().msg("高模具不能放入低模具托盘");
            }*/

            List<String> moldBarcodeList = bo.getMoldBarcodeList();
            if (moldBarcodeList.size() != moldBarcodeList.stream().distinct().collect(Collectors.toList()).size()) {
                return Result.err().msg("模具存在重复");
            }

            List<BaseMold> baseMoldBarcodeList = baseMoldService.queryByMoldBarcodeList(moldBarcodeList);
            if (CollectionUtils.isEmpty(baseMoldBarcodeList)) {
                return Result.err().msg("条码不存在，请重新扫描");
            }
            //计算托盘上的模具是否超出标准数量
            Double targetPoint  = Double.valueOf(isysParameterService.selectConfigByKey(ParameterConstants.ParameterSystem.moldDieWeight));
            double sumWeight = baseMoldBarcodeList.stream().mapToDouble(x -> x.getWeight()==null ?0:x.getWeight()).sum();
            if (sumWeight>targetPoint){
                return Result.err().msg("模具重量超出标准重量");
            }

            List<BusinOutInPlan> inPlans = new ArrayList<>();
            Long no = IntIdUtil.generateIntId();;
            for (String moldBarcode : moldBarcodeList) {
                BaseMold barcode = baseMoldBarcodeList.stream().filter(x -> x.getBarCode().equals(moldBarcode)).findFirst().orElse(null);
                if (barcode == null) {
                    return Result.err().msg("key条码不存在，请重新扫描", moldBarcode);
                }
                BusinOutInPlan inPlan = new BusinOutInPlan();
                inPlan.setOrderNo(no);
                inPlan.setMoldBarcode(moldBarcode);
                inPlan.setMoldId(barcode.getId());
                inPlan.setMoldNo(barcode.getMoldNo());
                inPlan.setContainerId(container.getId());
                inPlan.setContainerCode(container.getContainerCode());
                inPlan.setType(PlanTypeEnum.IN.getValue());
                inPlan.setPlanStatus(PlanStatusEnum.WAIT.getValue());
                inPlan.setQty(BigDecimal.ONE);
                inPlan.setBranchId(3L);
                inPlans.add(inPlan);
            }

            //从redis中获取有没有回库的模具
            List<SysStockDetail> sysStockDetailList = redisCache.getCacheList(RedisLockConstants.containerCode + "_" + bo.getContainerCode());
            if (CollectionUtils.isNotEmpty(sysStockDetailList)) {
                log.info("回库任务转化为入库任务 = {}", sysStockDetailList);
                List<BusinOutInPlan> convertInPlan = returnTaskConvertInPlan(inPlans, sysStockDetailList, no, container.getId(), container.getContainerCode());
                if (CollectionUtils.isEmpty(convertInPlan)) {
                    log.info("回库是空的托盘 不需要添加了");
                }
                inPlans.addAll(convertInPlan);
            }

            //找货位
            StockDetailLocationVo location = sysStockDetailService.findLocationByContainer(container.getContainerCode(),  SecurityUtils.erpBranchId);
            if (location == null) {
                return Result.err().msg("没有可入库的货位");
            }
            log.info("找货位的id  = {}", location.getId());
            inPlans.forEach(x -> x.setLocationId(location.getId()));
            this.saveBatch(inPlans);

            //增加预库存
            sysStockDetailService.addStockByIn(inPlans, location, false);

            if (Objects.equals(bo.getIsCall(), YesAndNoEnum.YES.getValue())) {
                // wcs呼叫托盘入库 默认呼叫在库的空治具，如果无空治具，在呼叫托盘模具体积最小的；
                //10.8 不在自动呼叫空托盘，因为不知道要入库的模具类型
                //  callEmptyLocationNotContainerCode(containerCode);
            }

            if (CollectionUtils.isNotEmpty(sysStockDetailList)) {
                //删除对应的redis
                redisCache.deleteObject(RedisLockConstants.containerCode + "_" + bo.getContainerCode());
            }

        }catch (Exception e){
            return Result.err().msg("模具库入库异常，"+e.getMessage());
        }finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
        return Result.ok();
    }


    /**
     * 获取托盘是否有未完成的入库任务
      * @param containerCode
     * @param planType
     * @return
     */
    private BusinOutInPlan getOrderByContainerCode(String containerCode, String planType){

         // 如果没有指定计划类型 就默认是入库
         if (StringUtils.isBlank(planType)) {
             planType = PlanTypeEnum.IN.getValue();
         }
        LambdaQueryWrapper<BusinOutInPlan> wq = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getContainerCode, containerCode)
                .eq(BusinOutInPlan::getType, planType)
                .notIn(BusinOutInPlan::getPlanStatus,PlanStatusEnum.FINISH.getValue(),PlanStatusEnum.CANCEL.getValue())
                .orderByDesc(BusinOutInPlan::getCreateTime)
                .last("limit 1");
        BusinOutInPlan one = this.getOne(wq);
        return one;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> addInPlanByEmpty(List<BusinInPlanEmptyAddBo> bos) {
        List<BusinOutInPlan> inPlans = new ArrayList<>();
        for (BusinInPlanEmptyAddBo bo : bos) {
            Long orderNo = IntIdUtil.generateIntId();;
            BusinOutInPlan inPlan = new BusinOutInPlan();
            inPlan.setOrderNo(orderNo);
            inPlan.setContainerId(bo.getContainerId());
            inPlan.setContainerCode(bo.getContainerCode());
            inPlan.setType(PlanTypeEnum.EMPTY_IN.getValue());
            inPlan.setPlanStatus(PlanStatusEnum.WAIT.getValue());
            inPlan.setQty(BigDecimal.ZERO);
            inPlan.setBranchId(3L);
            StockDetailLocationVo location = sysStockDetailService.findLocationByContainer(bo.getContainerCode(),  SecurityUtils.erpBranchId);
            inPlan.setLocationId(location.getId());
            inPlan.setLayer(1);
            inPlans.add(inPlan);
        }

       return this.saveBatch(inPlans)?Result.ok():Result.err().msg("空托入库失败");
    }


    @Override
    public Result<Boolean> addInPlanTime(BusinInPlanTimeBo bo) {

        List<String> moldBarcodeList = bo.getMoldBarcodeList();
        if (moldBarcodeList.size() != moldBarcodeList.stream().distinct().collect(Collectors.toList()).size()) {
            return Result.err().msg("模具存在重复");
        }
        List<BaseMold> baseMoldBarcodeList = baseMoldService.queryByMoldBarcodeList(moldBarcodeList);
        if (CollectionUtils.isEmpty(baseMoldBarcodeList)) {
            return Result.err().msg("条码不存在，请重新扫描");
        }

        //验证计划出库的条码是否已存在计划
        List<BusinOutInPlan> businOutInPlans = queryByMoldBarcodeList(moldBarcodeList,PlanTypeEnum.INPLAN.getValue());
        StringBuilder sb = new StringBuilder();
        if(businOutInPlans.size()>0){
            for (BusinOutInPlan businOutInPlan : businOutInPlans) {
                sb.append(businOutInPlan.getMoldNo());
                sb.append("/");
            }
            return Result.err().msg(sb.substring(0,sb.length()-1)+"已存在入库计划");
        }

        List<BusinOutInPlan> inPlans = new ArrayList<>();
        Long no = IntIdUtil.generateIntId();;
        for (String moldBarcode : moldBarcodeList) {
            BaseMold barcode = baseMoldBarcodeList.stream().filter(x -> x.getBarCode().equals(moldBarcode)).findFirst().orElse(null);
            if (barcode == null) {
                return Result.err().msg("key条码不存在，请重新扫描", moldBarcode);
            }
            BusinOutInPlan inPlan = new BusinOutInPlan();
            inPlan.setOrderNo(no);
            inPlan.setMoldBarcode(moldBarcode);
            inPlan.setMoldId(barcode.getId());
            inPlan.setMoldNo(barcode.getMoldNo());
            inPlan.setType(PlanTypeEnum.INPLAN.getValue());
            inPlan.setPlanStatus(PlanStatusEnum.WAIT.getValue());
            inPlan.setQty(BigDecimal.ONE);
            inPlan.setBranchId(SecurityUtils.getCurrentBranchId());
            inPlan.setPlanTime(bo.getPlanTime());
            inPlans.add(inPlan);
        }
       return this.saveOrUpdateBatch(inPlans)?Result.ok():Result.err();


    }


    /**
     * 呼叫空托盘(弃用)
     *
     * @return {@link Result}<{@link Boolean}>
     */
    @Override
    public Result<Boolean> callEmptyContainer() {
        return null;
      //  return callEmptyLocationNotContainerCode(null);
    }

    @Override
    public Result<Boolean> callEmptyContainerByQty(BaseContainerCallBo bo) {
        //如果呼叫指定的托盘，先判断是否已有未执行的任务
        if (bo.getContainerCode() != null){
            BusinOutInPlan businOutInPlan = this.getOrderByContainerCode(bo.getContainerCode(), PlanTypeEnum.CALL.getValue());
            if (businOutInPlan != null ){
                return Result.err().msg("此托盘已呼叫，等待出库");
            }
        }

        StockDetailMoldVo vo = sysStockDetailService.findLocationNotContainerCode(bo);
        if (vo == null) {
            return Result.err().msg("没有可容纳所需数量的托盘："+bo.getModlQty());
        }


        String containerCode = vo.getContainerCode();
        Long containerId = vo.getContainerId();

        //更新货位库存状态为预占
        List<SysStockDetail> sysStockDetails = sysStockDetailService.getByContainerId(containerId);
        if (sysStockDetails !=null && sysStockDetails.size()>0){
            for (SysStockDetail sysStockDetail : sysStockDetails) {
                sysStockDetail.setOutQty(new BigDecimal("-1"));
                sysStockDetailService.updateById(sysStockDetail);
            }
        }
        sysStockDetailService.updateBatchById(sysStockDetails);

        Long orderNo = IntIdUtil.generateIntId();
        //新增呼叫空托盘单据，BusinOutInPlan
        BusinOutInPlan businOutInPlan = initCallPlan(containerId, containerCode, orderNo,bo.getModlQty());
        businOutInPlan.setLocationId(vo.getId());
        businOutInPlan.setDetailId(vo.getId());
        businOutInPlan.setLayer(bo.getLayer());

        OutStockDetailVo detailVo = new OutStockDetailVo();
        detailVo.setLocationId(vo.getId());
        detailVo.setLocationCode(vo.getLocationCode());
        detailVo.setLocationRow(vo.getLocationRow());
        detailVo.setLayer(vo.getLayer());
        detailVo.setLocationColumn(vo.getLocationColumn());
        detailVo.setOrderNo(orderNo);
        detailVo.setBranchId(vo.getBranchId());
        //给堆垛机下命令
        Result result = wmsCallWcsUtil.callOutStock(detailVo,bo.getLayer());
        if (Result.isOk(result)) {
            //正常更新单据状态为：出库中
            businOutInPlan.setPlanStatus(PlanStatusEnum.DOING.getValue());
        }
        this.save(businOutInPlan);

        //更新当前出库的托盘状态为，空托出库中
        BaseContainer baseContainer = baseContainerService.getById(containerId);
        baseContainer.setStatus(ContainerStausEnum.CALL.getValue());
        return baseContainerService.updateById(baseContainer)?Result.ok():Result.err();
    }


    @Override
    public List<BusinOutInPlan> queryNoFinishByLocationId(Long locationId) {
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getLocationId, locationId)
                .ne(BusinOutInPlan::getPlanStatus, PlanStatusEnum.FINISH.getValue())
                .ne(BusinOutInPlan::getPlanStatus, PlanStatusEnum.CANCEL.getValue());
        return list(wrapper);
    }

    @Override
    public List<BusinOutInPlan> queryNoFinishByContainerId(Long containerId) {
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getContainerId, containerId)
                .ne(BusinOutInPlan::getPlanStatus, PlanStatusEnum.FINISH.getValue())
                .ne(BusinOutInPlan::getPlanStatus, PlanStatusEnum.CANCEL.getValue());
        return list(wrapper);
    }

    /**
     * 根据任务编号重新分配高货位
     *
     * @param bo 薄
     * @return {@link Result}<{@link Boolean}>
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> repeatFindHighLocation(UpdPlanLocationBo bo) {
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getOrderNo, bo.getOrderNo())
                .ne(BusinOutInPlan::getPlanStatus, PlanStatusEnum.FINISH.getValue())
                .ne(BusinOutInPlan::getPlanStatus, PlanStatusEnum.CANCEL.getValue());
        List<BusinOutInPlan> list = list(wrapper);

        if (CollectionUtils.isEmpty(list)) {
            return Result.err().msg("不存在该任务");
        }
        List<BaseMold> baseMoldBarcodeList = baseMoldService.queryByMoldBarcodeList(list.stream().map(x -> x.getMoldBarcode()).collect(Collectors.toList()));


        //重新找高货位
        StockDetailLocationVo location = sysStockDetailService.findLocationByContainer(list.get(0).getContainerCode(),  list.get(0).getBranchId());
        if (location == null) {
            return Result.err().msg("没有可入库的货位");
        }
        Long oldLocationId = list.get(0).getLocationId();
        log.info("原始货位 = {} 找货位的id  = {}", oldLocationId, location.getId());
        list.forEach(x -> x.setLocationId(location.getId()));

        //库存需要变化
        List<SysStockDetail> detailList = sysStockDetailService.getByContainerId(list.get(0).getContainerId());
        detailList.forEach(x -> x.setLocationId(location.getId()).setLocationCode(location.getLocationCode()));
        this.updateBatchById(list);
        sysStockDetailService.updateBatchById(detailList);
        return Result.ok();
    }

    @Override
    public List<BusinTaskManageVo> getTaskManagement() {
        return businOutInPlanMapper.getTaskManagement();
    }

    @Override
    public List<BusinDataTaskVo> getTaskInfo(BusinTaskQueryBo bo) {
        return businOutInPlanMapper.getTaskInfo(bo);
    }

    @Override
    public BusinOutInPlan getOrderNoByOrderNo(String orderNo, String planStatus) {

        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getOrderNo, orderNo)
                //.eq(BusinOutInPlan::getPlanStatus,planStatus)
                .last("limit 1");
        BusinOutInPlan one = getOne(wrapper);
        return one;
    }

    @Override
    public Boolean updateSysStockByPlanOrderNo(String orderNo) {

        LambdaQueryWrapper<BusinOutInPlan> eq = new LambdaQueryWrapper<BusinOutInPlan>().eq(BusinOutInPlan::getOrderNo, orderNo)
                .ne(BusinOutInPlan::getPlanStatus, PlanStatusEnum.FINISH.getValue());
        List<BusinOutInPlan> list = list(eq);
        //任务已经完成或者不存在该任务
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }

        log.info("入库失败清理的信息{}",list);
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getOrderNo, orderNo)
                .last("limit 1");
        BusinOutInPlan one = getOne(wrapper);
        Long containerId = one.getContainerId();


        if ((one.getType().equals(PlanTypeEnum.BACK.getValue()) && StringUtils.isNotEmpty(one.getMoldNo())) || one.getType().equals(PlanTypeEnum.IN.getValue())){
            List<SysStockDetail> sysStockDetails = sysStockDetailService.getByContainerId(containerId);
            boolean b = sysStockDetailService.removeByIds(sysStockDetails.stream().map(x -> x.getId()).collect(Collectors.toList()));
            if (b){
                log.info("清理预占入库库存成功{}",sysStockDetails);
            }else {
                log.info("清理预占入库库存失败{}",sysStockDetails);
            }
            return b;
        }
        else {
            return false;
        }
    }


    /**
     * 呼叫托盘入库
     *
     * @param containerCode 托盘代码
     */
/*    private Result callEmptyLocationNotContainerCode(String containerCode) {
        log.info("呼叫托盘  = {}", containerCode);
        StockDetailLocationVo vo = sysStockDetailService.findLocationNotContainerCode(containerCode);
        if (vo == null) {
            log.info("没有货位可以呼叫了 = {}", containerCode);
            return Result.err().msg("没有货位可以呼叫了");
        }
        String code = CollectionUtils.isEmpty(vo.getStockDetailList()) ? "" : vo.getStockDetailList().get(0).getContainerCode();
        Long containerId = CollectionUtils.isEmpty(vo.getStockDetailList()) ? 0L : vo.getStockDetailList().get(0).getContainerId();
        Long orderNo = IntIdUtil.generateIntId();;
        BusinOutInPlan businOutInPlan = initCallPlan(containerId, StringUtils.isNoneBlank(containerCode) ? containerCode : code, orderNo,0);
        businOutInPlan.setLocationId(vo.getId());
        OutStockDetailVo detailVo = new OutStockDetailVo();
        detailVo.setLocationId(vo.getId());
        detailVo.setLocationCode(vo.getLocationCode());
        detailVo.setLocationRow(vo.getLocationRow());
        detailVo.setLayer(vo.getLayer());
        detailVo.setLocationColumn(vo.getLocationColumn());
        detailVo.setOrderNo(orderNo);
        detailVo.setBranchId(vo.getBranchId());
        Result result = wmsCallWcsUtil.callOutStock(detailVo);
        if (Result.isOk(result)) {
            businOutInPlan.setPlanStatus(PlanStatusEnum.DOING.getValue());
        }
        this.save(businOutInPlan);

        //并且这个时候不需要做库存处理、因为是空托盘 所以直接删除库存记录
        sysStockDetailService.deleteByLocationId(vo.getId());
        return Result.ok();
    }*/

    /**
     * 确认出库
     *
     * @param bos 薄
     * @return {@code Result<Boolean>}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> confirmOut(List<BusinOutPlanConfirmBo> bos) {
        final LockInfo lockInfo = lockTemplate.lock("confirmOut", RedisLockConstants.expire, RedisLockConstants.acquireTimeout, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }
        try {
            List<BusinOutInPlan> updList = new ArrayList<>();
            for (BusinOutPlanConfirmBo bo : bos) {
                // 也就是确认出库  就是实际上把库存减掉就行了  不需要和wcs交互了
                BusinOutInPlan plan = getById(bo.getId());
                if (plan == null) {
                    return Result.err().msg("出库计划不存在");
                }
                if (!plan.getMoldBarcode().equals(bo.getMoldBarcode())) {
                    return Result.err().msg("模具编码不正确");
                }
                if (!plan.getPlanStatus().equals(PlanStatusEnum.DOING.getValue())) {
                    return Result.err().msg("出库计划状态异常");
                }

                plan.setPlanStatus(PlanStatusEnum.FINISH.getValue());
                updList.add(plan);
            }

            log.info("要删除的库存明细："+updList);

            //如果存在相同的托盘中的有待出库的任务 就需要更新为执行中
            LambdaQueryWrapper<BusinOutInPlan> in = new LambdaQueryWrapper<BusinOutInPlan>()
                    .eq(BusinOutInPlan::getType, PlanTypeEnum.OUT.getValue())
                    .eq(BusinOutInPlan::getPlanStatus, PlanStatusEnum.WAIT.getValue())
                    .in(BusinOutInPlan::getContainerId, updList.stream().map(x -> x.getContainerId()).collect(Collectors.toList()));
            List<BusinOutInPlan> list = list(in);
            if (CollectionUtils.isNotEmpty(list)) {
                log.info("此托盘有待执行的任务{}",list);
                list.forEach(x -> x.setPlanStatus(PlanStatusEnum.DOING.getValue()));
                this.updateBatchById(list);

                //把待执行的任务状态改为成功
                LambdaQueryWrapper<SysThirdExceptionRequest> ster = new LambdaQueryWrapper<SysThirdExceptionRequest>()
                            .eq(SysThirdExceptionRequest::getStatus,  ThirdApiStatusEnum.fail.getValue())
                            .in(SysThirdExceptionRequest::getCode, list.stream().map(x -> x.getOrderNo()).collect(Collectors.toList()));
                List<SysThirdExceptionRequest> sterList = sysThirdExceptionRequestService.list(ster);
                if (CollectionUtils.isNotEmpty(sterList)){
                    //待执行的任务信息
                    log.info("待执行的任务信息{}",sterList);
                   sterList.forEach(x ->x.setStatus(ThirdApiStatusEnum.success.getValue()));
                   sysThirdExceptionRequestService.updateBatchById(sterList);
                }
            }
            this.updateBatchById(updList);
            //实际减库存
            updList.forEach(plan -> sysStockDetailService.subtractStockByOut(plan, plan.getDetailId(), true));

            //判断当前所有的托盘的任务全部完成、才可以进行删除库存、等待回库
            Map<Long, List<BusinOutInPlan>> map = updList.stream().collect(Collectors.groupingBy(x -> x.getContainerId()));
            for (Map.Entry<Long, List<BusinOutInPlan>> entry : map.entrySet()) {

                List<BusinOutInPlan> containerTask = list(new LambdaQueryWrapper<BusinOutInPlan>()
                        .eq(BusinOutInPlan::getType, PlanTypeEnum.OUT.getValue())
                        .notIn(BusinOutInPlan::getPlanStatus, PlanStatusEnum.FINISH.getValue(),PlanStatusEnum.CANCEL.getValue())
                        .eq(BusinOutInPlan::getContainerId, entry.getKey()));
                log.info("未完成的托盘任务信息{}",containerTask);
                String containerCode = entry.getValue().get(0).getContainerCode();
                if (CollectionUtils.isNotEmpty(containerTask)) {
                    log.info("托盘还有任务没有完成 = {}", containerCode);
                    continue;
                }
                log.info("托盘出库任务全部完成 进行库存变化 = {}", containerCode);

                List<SysStockDetail> detailList = sysStockDetailService.getByContainerId(entry.getKey());
                // 剩余的库存存放于redis中、如果后续有入库计划进行添加、则回库任务变成入库任务、 没有就直接回库即可

                redisCache.setCacheList(RedisLockConstants.containerCode + "_" + containerCode, detailList);
                sysStockDetailService.removeByIds(detailList.stream().map(x -> x.getId()).collect(Collectors.toList()));
            }
            //堆垛机非自动完成状态后，把redis中的ID删除，代表此任务已经完成
            Object taskOutId = redisTemplate.opsForValue().get(RedisConstant.redisTaskOut);
            Long orderNo = updList.get(0).getOrderNo();
            if (taskOutId !=null && String.valueOf(taskOutId).equals(orderNo.toString())){
                log.info("出库完成时判断redis中的数据是否还存在，存在则删除，删除redis中的出库任务状态记录：",orderNo);
                redisTemplate.delete(RedisConstant.redisTaskOut);
            }

        }catch (Exception e){
            log.info("出库确认异常 手动回滚 {}", bos+e.getMessage());
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }finally {
            lockTemplate.releaseLock(lockInfo);
        }
        return Result.ok();

    }

    /**
     * 下发出库任务
     *
     * @param bo 薄
     * @return {@code Result<Boolean>}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> addOutPlan(BusinOutPlanAddBo bo) {
        final LockInfo lockInfo = lockTemplate.lock("addOutPlan", RedisLockConstants.expire, RedisLockConstants.acquireTimeout, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }
        try {
            List<String> moldBarcodeList = bo.getMoldBarcodeList();
            List<BaseMold> baseMoldBarcodeList = baseMoldService.queryByMoldBarcodeList(moldBarcodeList);
            if (CollectionUtils.isEmpty(baseMoldBarcodeList)) {
                return Result.err().msg("条码不存在，请重新扫描");
            }
            List<BusinOutInPlan> outPlans = new ArrayList<>();

            List<OutStockDetailVo> outStockDetailVos = new ArrayList<>();
            List<SysStockDetailVo> stockDetailVos = new ArrayList<>();

            for (String moldBarcode : moldBarcodeList) {
                BaseMold barcode = baseMoldBarcodeList.stream().filter(x -> x.getBarCode().equals(moldBarcode)).findFirst().orElse(null);
                if (barcode == null) {
                    return Result.err().msg("key条码不存在，请重新扫描", moldBarcode);
                }

                //选择的物料是否存在正在执行的出库任务,排除已完成和作废的
                LambdaQueryWrapper<BusinOutInPlan> outWrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                        .eq(BusinOutInPlan::getType, PlanTypeEnum.OUT.getValue())
                        .eq(BusinOutInPlan::getMoldBarcode,moldBarcode)
                        .notIn(BusinOutInPlan::getPlanStatus,PlanStatusEnum.FINISH.getValue(),PlanStatusEnum.CANCEL.getValue())
                        .last("limit 1");
                BusinOutInPlan one = this.getOne(outWrapper);
                if (one != null){
                    return Result.err().msg("Outbound in progress, no need to issue outbound task:"+one.getMoldNo());
                }

                BusinOutInPlan outPlan = new BusinOutInPlan();
                outPlan.setLayer(bo.getLayer());
                outPlan.setMoldBarcode(moldBarcode);
                outPlan.setMoldId(barcode.getId());
                outPlan.setMoldNo(barcode.getMoldNo());
                outPlan.setType(PlanTypeEnum.OUT.getValue());

                outPlan.setQty(BigDecimal.ONE);
                outPlan.setBranchId(SecurityUtils.erpBranchId);
                //找库存
                Result<SysStockDetailVo> voResult = sysStockDetailService.findOutLocation(outPlan);
                if (!Result.isOk(voResult)) {
                    log.info("库存不够 手动回滚 {}", voResult);
                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                    return Result.err().msg(voResult.getMsg());
                }
                SysStockDetailVo data = voResult.getData();
                outPlan.setLocationId(data.getLocationId());
                outPlan.setContainerId(data.getContainerId());
                outPlan.setContainerCode(data.getContainerCode());
                outPlan.setPlanStatus(PlanStatusEnum.DOING.getValue());
                // 这保存到表里
                outPlan.setDetailId(data.getId());

                //预出库
                sysStockDetailService.subtractStockByOut(outPlan, data.getId(), false);
                outPlans.add(outPlan);

                stockDetailVos.add(data);

            }
            Map<Long, List<BusinOutInPlan>> listMap = outPlans.stream().collect(Collectors.groupingBy(BusinOutInPlan::getLocationId));
            for (Map.Entry<Long, List<BusinOutInPlan>> entry : listMap.entrySet()) {
                Long orderNo = IntIdUtil.generateIntId();;
                entry.getValue().forEach(x -> x.setOrderNo(orderNo));
            }
            this.saveBatch(outPlans);

            Integer index = 0;
            for (Map.Entry<Long, List<BusinOutInPlan>> entry : listMap.entrySet()) {
    //            Long orderNo = IntIdUtil.generateIntId();;
    //            entry.getValue().forEach(x -> x.setOrderNo(orderNo));

                SysStockDetailVo vo = stockDetailVos.stream().filter(x -> x.getLocationId().equals(entry.getKey())).findFirst().orElse(null);
                OutStockDetailVo detailVo = new OutStockDetailVo();
                detailVo.setLocationId(vo.getLocationId());
                detailVo.setLocationCode(vo.getLocationCode());
                detailVo.setLocationRow(vo.getLocationRow());
                detailVo.setLayer(vo.getLayer());
                detailVo.setLocationColumn(vo.getLocationColumn());
                detailVo.setOrderNo(entry.getValue().get(0).getOrderNo());
                detailVo.setBranchId(vo.getBranchId());
                outStockDetailVos.add(detailVo);
                log.info("开始调用wcs下发任务 = {}", detailVo);
                //由于堆垛机只能执行一个任务、所以这里只能第一个任务下发、其他的任务进行轮询
                if (index.equals(0)) {
                    Result result = wmsCallWcsUtil.outStock(detailVo, true,bo.getLayer());
                    if (!Result.isOk(result)) {
                        entry.getValue().forEach(x -> x.setPlanStatus(PlanStatusEnum.WAIT.getValue()));
                    } else {
                        entry.getValue().forEach(x -> x.setPlanStatus(PlanStatusEnum.DOING.getValue()));
                    }
                } else {
                    log.info("不用调用、直接进入轮训 = {}", detailVo);
                    entry.getValue().forEach(x -> x.setPlanStatus(PlanStatusEnum.WAIT.getValue()));
                    wmsCallWcsUtil.outStock(detailVo, false, bo.getLayer());
                }
                index++;

            }
            this.updateBatchById(outPlans);
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> addOutPlanTime(BusinOutPlanAddBo bo) {
        try {
            List<String> moldBarcodeList = bo.getMoldBarcodeList();
            List<BaseMold> baseMoldBarcodeList = baseMoldService.queryByMoldBarcodeList(moldBarcodeList);
            if (CollectionUtils.isEmpty(baseMoldBarcodeList)) {
                return Result.err().msg("条码不存在，请重新扫描");
            }

            //验证计划入库的条码是否已存在计划
            List<BusinOutInPlan> businOutInPlans = queryByMoldBarcodeList(moldBarcodeList,PlanTypeEnum.OUTPLAN.getValue());
            StringBuilder sb = new StringBuilder();
            if(businOutInPlans.size()>0){
                for (BusinOutInPlan businOutInPlan : businOutInPlans) {
                    sb.append(businOutInPlan.getMoldNo());
                    sb.append("/");
                }
                return Result.err().msg(sb.substring(0,sb.length()-1)+"已存在出库计划");
            }
            //根据所做的出库模具，查询模具所在的托盘，根据所在的托盘生成不同的单据
            List<SysStockDetail> sysStockDetails = sysStockDetailService.queryByMoldBarcodeLocationList(moldBarcodeList);

            Long oldLocationId =0L;
            List<BusinOutInPlan> outPlans = new ArrayList<>();
            Long orderNo = 0L;
            for (SysStockDetail sysStockDetail : sysStockDetails) {
                Long locationId = sysStockDetail.getLocationId();

                BusinOutInPlan outPlan = new BusinOutInPlan();
                outPlan.setMoldBarcode(sysStockDetail.getBarcode());
                outPlan.setMoldId(sysStockDetail.getMoldId());
                outPlan.setMoldNo(sysStockDetail.getMoldNo());
                outPlan.setType(PlanTypeEnum.OUT.getValue());
                outPlan.setLocationId(locationId);
                outPlan.setDetailId(sysStockDetail.getId());
                outPlan.setContainerId(sysStockDetail.getContainerId());
                outPlan.setContainerCode(sysStockDetail.getContainerCode());
                outPlan.setQty(BigDecimal.ONE);
                outPlan.setBranchId(SecurityUtils.erpBranchId);
                outPlan.setPlanTime(bo.getPlanTime());
                outPlan.setPlanStatus(PlanStatusEnum.WAIT.getValue());
                outPlan.setLayer(bo.getLayer());

                if (!oldLocationId.equals(locationId)){
                    orderNo=IntIdUtil.generateIntId();
                    BaseLocation baseLocation = baseLocationService.getById(locationId);
                    STKMoveBo moveBo = new STKMoveBo();
                    moveBo.setTaskId(orderNo);
                    moveBo.setFromRow(baseLocation.getLocationRow());
                    moveBo.setFromColumn(baseLocation.getLocationColumn());
                    moveBo.setFromLayer(baseLocation.getLayer());
                    thirdExceptionRequestService.addException(orderNo.toString(), ThirdApiTypeEnum.wcsOutTask, JSONObject.toJSONString(moveBo),
                            "出库计划", false,bo.getPlanTime(),bo.getLayer());
                }
                outPlan.setOrderNo(orderNo);
                outPlans.add(outPlan);
                oldLocationId=locationId;

                sysStockDetailService.subtractStockByOut(outPlan, sysStockDetail.getId(), false);

            }
            this.saveBatch(outPlans);

        } catch (Exception e){
           log.error("新增出库计划失败："+e.getMessage());
        }
        return Result.ok();
    }


    /**
     * 检查运行任务时候可以下发  Result.ok();不需要下发了 否则需要下发
     *
     * @param orderNo 订单没有
     * @return {@link Result}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result checkIsRunTask(Long orderNo) {
        BusinOutInPlan one = getOne(new LambdaQueryWrapper<BusinOutInPlan>().eq(BusinOutInPlan::getOrderNo, orderNo).last("limit 1"));
        if (one == null) {
            return Result.err();
        }
        // 判断当前是否有正在执行的出库计划 或者库存没有数据  就不用下发了
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>().ne(BusinOutInPlan::getOrderNo, orderNo)
                .eq(BusinOutInPlan::getPlanStatus, PlanStatusEnum.DOING.getValue())
                .eq(BusinOutInPlan::getType, PlanTypeEnum.OUT.getValue())
                .eq(BusinOutInPlan::getContainerId, one.getContainerId());
        List<BusinOutInPlan> list = list(wrapper);
        if (CollectionUtils.isNotEmpty(list)) {
            log.info("当前托盘存在正在执行的任务、可以不用下发了 = {}", one);
            return Result.ok();
        }
        // 查询库存
        List<SysStockDetail> stockDetailList = sysStockDetailService.getByContainerId(one.getContainerId());
        if (CollectionUtils.isEmpty(stockDetailList)) {
            log.info("当前托盘没有对应的库存货位了、可以不用下发了 = {}", one);
            return Result.ok();
        }
        return Result.err();
    }

    /**
     * 取消 (已废弃)
     *
     * @param id id
     * @return {@code Result<Boolean>}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> cancel(Long id) {
        BusinOutInPlan one = getById(id);
        if (one == null) {
            return Result.err().msg("计划不存在");
        }
        if (!one.getPlanStatus().equals(PlanStatusEnum.WAIT.getValue())) {
            return Result.err().msg("只有待出入库状态才能取消");
        }
        one.setPlanStatus(PlanStatusEnum.CANCEL.getValue());
        this.updateById(one);

        //  预占库存取消
        LambdaQueryWrapper<SysStockDetail> wrapper = new LambdaQueryWrapper<SysStockDetail>().eq(SysStockDetail::getBarcode, one.getMoldBarcode())
                .eq(SysStockDetail::getLocationId,one.getLocationId())
                .eq(SysStockDetail::getContainerId,one.getContainerId());
        SysStockDetail sysStockDetail = sysStockDetailService.getOne(wrapper);

        boolean b = sysStockDetailService.removeById(sysStockDetail.getId());
        if (b){
            log.info("入库取消，删除预占库存成功"+JsonUtil.toJSONString(sysStockDetail));
        }else {
            log.info("入库取消，删除预占库存失败"+JsonUtil.toJSONString(sysStockDetail));
        }
        return Result.ok();
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> outCancel(Long id) {
        BusinOutInPlan one = getById(id);
        log.info("取消出库计划数据{}",one);
        if (one == null) {
            return Result.err().msg("计划不存在");
        }
        if (!one.getPlanStatus().equals(PlanStatusEnum.WAIT.getValue())) {
            return Result.err().msg("只有待出库状态才能取消");
        }
        one.setPlanStatus(PlanStatusEnum.CANCEL.getValue());
        this.updateById(one);

        //  预占库存取消
        LambdaQueryWrapper<SysStockDetail> wrapper = new LambdaQueryWrapper<SysStockDetail>()
                .eq(SysStockDetail::getBarcode, one.getMoldBarcode())
                .eq(SysStockDetail::getLocationId,one.getLocationId())
                .eq(SysStockDetail::getContainerId,one.getContainerId());
        SysStockDetail sysStockDetail = sysStockDetailService.getOne(wrapper);
        sysStockDetail.setOutQty(BigDecimal.ZERO);
        log.info("取消出库预占库存信息{}",sysStockDetail);

        boolean b = sysStockDetailService.updateById(sysStockDetail);
        if (b){
            log.info("出库取消，更新预占库存成功"+JsonUtil.toJSONString(sysStockDetail));
        }else {
            log.info("出库取消，更新预占库存失败"+JsonUtil.toJSONString(sysStockDetail));
        }

        //判断整个出库任务是否还有模具，如果没有，则把定时的数据改为成功
        Long orderNo = one.getOrderNo();
        LambdaQueryWrapper<BusinOutInPlan> planWrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getOrderNo,orderNo);
        List<BusinOutInPlan> list = this.list(planWrapper);
        log.info("要取消的出库任务{}",list);
        List<BusinOutInPlan> collect = list.stream().filter(x -> !x.getPlanStatus().equals(PlanStatusEnum.CANCEL.getValue())).collect(Collectors.toList());
        log.info("要取消的任务，过滤掉已取消的{}",collect);
        //整个单子都是取消状态，则把定时任务给取消掉
        if (collect !=null && collect.size() ==0){

            LambdaQueryWrapper<SysThirdExceptionRequest> sysWrapper = new LambdaQueryWrapper<SysThirdExceptionRequest>()
                    .eq(SysThirdExceptionRequest::getCode,orderNo);
            SysThirdExceptionRequest requestServiceOne = sysThirdExceptionRequestService.getOne(sysWrapper);
            requestServiceOne.setStatus(ThirdApiStatusEnum.success.getValue());
            requestServiceOne.setResponseBody("出库计划全部取消");
            requestServiceOne.setUpdateTime(DateUtils.getNowDate());
            sysThirdExceptionRequestService.updateById(requestServiceOne);
        }

        return Result.ok();
    }

    /**
     * 通过托盘编码查询订单号
     *
     * @param containerCode 托盘代码
     * @param planType
     * @return {@code Result<String>}
     */
    @Override
    public Result<PlanOrderLocationVo> getOrderNoByContainerCode(String containerCode, String planType) {
        //判断托盘是否存在
        BaseContainer container = baseContainerService.queryContainerByCode(containerCode);
        if (container == null) {
            return Result.err().msg("托盘不存在");
        }

        // 如果没有指定计划类型 就默认是入库
        if (StringUtils.isBlank(planType)) {
            planType = PlanTypeEnum.IN.getValue();
        }
        //正在执行入库计划,如果有多个入库则取最新一次入库的；
        LambdaQueryWrapper<BusinOutInPlan> eq = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getContainerCode, containerCode)
                .in(BusinOutInPlan::getPlanStatus,PlanStatusEnum.WAIT.getValue(),PlanStatusEnum.DOING.getValue())
                //.eq(BusinOutInPlan::getPlanStatus, PlanStatusEnum.WAIT.getValue())
                .in(BusinOutInPlan::getType,PlanTypeEnum.IN.getValue(),PlanTypeEnum.BACK.getValue(),PlanTypeEnum.INV_BACK.getValue(),PlanTypeEnum.EMPTY_IN.getValue())
                .orderByDesc(BusinOutInPlan::getCreateTime)
                // .eq(BusinOutInPlan::getType, planType)
//                .last("limit 1")
                ;

        List<BusinOutInPlan> ones = this.list(eq);
        //如果没有查到相关任务，则新增入库计划
        if (CollectionUtils.isEmpty(ones)) {
            Result result = addReturnPlan(container);
            return result;
        }

        BusinOutInPlan one = ones.get(0);
        BaseLocation location = baseLocationService.getById(one.getLocationId());
        if (location == null) {
            return Result.err().msg("货位不存在");
        }
        PlanOrderLocationVo vo = new PlanOrderLocationVo();
        if (one.getType().equals(PlanTypeEnum.INV_BACK.getValue())){
            List<BusinStockInv> businStockInvs = iBusinStockInvService.queryStockInvList(one.getOrderNo().toString());
            long qtySum = businStockInvs.stream().mapToLong(x -> x.getQty().longValue()).sum();
            long invQtySum = businStockInvs.stream().mapToLong(x -> x.getInvQty().longValue()).sum();
            if (qtySum == invQtySum && qtySum !=0 && qtySum !=0){
                vo.setType(location.getType());
            }else if (qtySum != invQtySum && invQtySum ==0){
                vo.setType(LocationPlanTypeEnum.EMPTY.getValue());
            }else if (qtySum != invQtySum && invQtySum !=0){
                vo.setType(location.getType());
            }else if(qtySum ==invQtySum && qtySum==0 && qtySum ==0){
                vo.setType(LocationPlanTypeEnum.EMPTY.getValue());
            }
        }else {
            BigDecimal qty = ones.stream().map(x -> x.getQty()).reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setType(qty.compareTo(BigDecimal.ZERO) == 0 ? LocationPlanTypeEnum.EMPTY.getValue() : location.getType());
        }
        vo.setOrderNo(one.getOrderNo());
        return Result.ok().data(vo);

    }


    /**
     * 回库任务转换计划任务
     *
     * @param detailList    详细列表
     * @param no            没有
     * @param containerId   托盘id
     * @param containerCode 托盘代码
     * @param addInPlans    加入计划
     * @return {@link List}<{@link BusinOutInPlan}>
     */
    public List<BusinOutInPlan> returnTaskConvertInPlan(List<BusinOutInPlan> addInPlans, List<SysStockDetail> detailList, Long no, Long containerId, String containerCode) {
        List<SysStockDetail> newDetailList = detailList.stream().filter(x -> x.getQty().add(x.getInQty()).add(x.getOutQty()).compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
        List<BusinOutInPlan> inPlans = new ArrayList<>();
        for (SysStockDetail stockDetail : newDetailList) {
            if (addInPlans.stream().anyMatch(x -> x.getMoldBarcode().equals(stockDetail.getBarcode()))) {
                log.info("回库数据存在、又重新扫描 需要过滤 = {}", stockDetail);
                continue;
            }
            BusinOutInPlan inPlan = new BusinOutInPlan();
            inPlan.setOrderNo(no);
            inPlan.setMoldBarcode(stockDetail.getBarcode());
            inPlan.setMoldId(stockDetail.getMoldId());
            inPlan.setMoldNo(stockDetail.getMoldNo());
            inPlan.setContainerId(containerId);
            inPlan.setContainerCode(containerCode);
            inPlan.setType(PlanTypeEnum.IN.getValue());
            inPlan.setPlanStatus(PlanStatusEnum.WAIT.getValue());
            inPlan.setQty(stockDetail.getQty());
            inPlan.setBranchId(stockDetail.getBranchId());
            inPlans.add(inPlan);
        }
        return inPlans;
    }


    /**
     * 添加返回计划
     *
     * @param container 托盘
     */
    private Result<PlanOrderLocationVo> addReturnPlan(BaseContainer container) {
        log.info("addReturnPlan  = {}", container);
        //判断是否有已出库未完成的单据，状态：已完成，类型等于出库
        LambdaQueryWrapper<BusinOutInPlan> eq = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getContainerId, container.getId())
                .notIn(BusinOutInPlan::getPlanStatus,PlanStatusEnum.FINISH.getValue(),PlanStatusEnum.CANCEL.getValue())
                .eq(BusinOutInPlan::getType, PlanTypeEnum.OUT.getValue());
        List<BusinOutInPlan> list = list(eq);
        if (CollectionUtils.isNotEmpty(list)) {
            log.info("存在对应的任务没有拣货完成 {}",list);
            return Result.err().msg("存在对应的任务没有拣货完成");
        }

        Boolean isEmpty = false;

        // 根据当前的托盘查询库存数据
        List<SysStockDetail> detailList = redisCache.getCacheList(RedisLockConstants.containerCode + "_" + container.getContainerCode());
//        List<SysStockDetail> detailList = sysStockDetailService.getByContainerId(container.getId());
        /*if (CollectionUtils.isEmpty(detailList)) {
            log.info("不需要处理数据了  = {}", container);
            isEmpty = true;
            //return Result.err().data(StringUtils.EMPTY);
        }*/
        //去掉库存为0的
        List<SysStockDetail> newDetailList=null;
        if ( !CollectionUtils.isEmpty(detailList) ){
            newDetailList = detailList.stream().filter(x -> x.getQty().add(x.getInQty()).add(x.getOutQty()).compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
        }


        if (CollectionUtils.isEmpty(newDetailList)) {
            isEmpty = true;
        }
        log.info("是否是空托盘了 = {}", isEmpty);


        List<BusinOutInPlan> inPlans = new ArrayList<>();
        Long no = IntIdUtil.generateIntId();;

        //托盘内的数据重新入库
        if ( !CollectionUtils.isEmpty(detailList) ) {
            for (SysStockDetail stockDetail : newDetailList) {
                BusinOutInPlan inPlan = new BusinOutInPlan();
                inPlan.setOrderNo(no);
                inPlan.setMoldBarcode(stockDetail.getBarcode());
                inPlan.setMoldId(stockDetail.getMoldId());
                inPlan.setMoldNo(stockDetail.getMoldNo());
                inPlan.setContainerId(container.getId());
                inPlan.setContainerCode(container.getContainerCode());
                inPlan.setType(PlanTypeEnum.BACK.getValue());
                inPlan.setPlanStatus(PlanStatusEnum.WAIT.getValue());
                inPlan.setQty(stockDetail.getQty());
                inPlan.setBranchId(stockDetail.getBranchId());
                inPlans.add(inPlan);
            }
        }

        //说明是空托盘，新增托盘回库信息
        if (CollectionUtils.isEmpty(inPlans)) {
            //新增空托盘信息
            BusinOutInPlan plan = initReturnPlan(container.getId(), container.getContainerCode(), no);
            //设置第一个模具即可 因为是空托盘了（）TODO 需要测试
           /* SysStockDetail stockDetail = detailList.get(0);
            plan.setMoldBarcode("")
                    .setMoldId(stockDetail.getMoldId())
                    .setMoldNo(stockDetail.getMoldNo());*/
            inPlans.add(plan);
        }

        StockDetailLocationVo location = null;
        if (isEmpty) {
            location = sysStockDetailService.findLocationByContainer(container.getContainerCode(), SecurityUtils.erpBranchId);
        } else {
            //找货位
            /*List<String> moldBarcodeList = newDetailList.stream().map(x -> x.getBarcode()).collect(Collectors.toList());
            List<BaseMold> baseMoldBarcodeList = baseMoldService.queryByMoldBarcodeList(moldBarcodeList);*/
            //baseMoldBarcodeList 一定都有库存的
            location = sysStockDetailService.findLocationByContainer(container.getContainerCode(),SecurityUtils.erpBranchId);
        }
        if (location == null) {
            return Result.err().msg("没有可入库的货位");
        }
        log.info("是否空货位 isEmpty = {} 回库找到的货位id = {}", isEmpty, location.getId());

        StockDetailLocationVo finalLocation = location;
        inPlans.forEach(x -> x.setLocationId(finalLocation.getId()));
        this.saveBatch(inPlans);


        //老数据需要清空 新货位先预占然后在实际回库的时候进行更新
//        sysStockDetailService.removeByIds(detailList.stream().map(x -> x.getId()).collect(Collectors.toList()));
//        detailList.forEach(detail -> {
//            detail.setQty(BigDecimal.ZERO)
//                    .setInQty(BigDecimal.ZERO)
//                    .setOutQty(BigDecimal.ZERO)
//                    .setContainerId(0L)
//                    .setContainerCode("-");
//        });
//        sysStockDetailService.updateBatchById(detailList);
        //增加预库存
        if (!isEmpty) {
            sysStockDetailService.addStockByIn(inPlans, location, false);
        }

        PlanOrderLocationVo vo = new PlanOrderLocationVo();
        vo.setType(isEmpty ? LocationPlanTypeEnum.EMPTY.getValue() : location.getType());
        vo.setOrderNo(no);

        if (CollectionUtils.isNotEmpty(detailList)) {
            //删除对应的redis
            redisCache.deleteObject(RedisLockConstants.containerCode + "_" + container.getContainerCode());
        }

        return Result.ok().data(vo);
    }


    private BusinOutInPlan initCallPlan(Long containerId, String containerCode, Long no,String moldQty) {
        //空托盘
        BusinOutInPlan inPlan = new BusinOutInPlan();
        inPlan.setOrderNo(no);
        inPlan.setContainerId(containerId);
        inPlan.setContainerCode(containerCode);
        inPlan.setType(PlanTypeEnum.CALL.getValue());
        inPlan.setPlanStatus(PlanStatusEnum.WAIT.getValue());
        if(StringUtils.isEmpty(moldQty)){
            moldQty="1";
        }
        inPlan.setQty(BigDecimal.valueOf(Double.valueOf(moldQty)));
        inPlan.setBranchId(SecurityUtils.getCurrentBranchId());
        return inPlan;
    }

    private BusinOutInPlan initReturnPlan(Long containerId, String containerCode, Long no) {
        //空托盘
        BusinOutInPlan inPlan = new BusinOutInPlan();
        inPlan.setOrderNo(no);
        inPlan.setContainerId(containerId);
        inPlan.setContainerCode(containerCode);
        inPlan.setType(PlanTypeEnum.BACK.getValue());
        inPlan.setPlanStatus(PlanStatusEnum.WAIT.getValue());
        inPlan.setQty(BigDecimal.ZERO);
        inPlan.setBranchId(3L);
        return inPlan;
    }


    /**
     * 实际添加库存
     *
     * @param bo 薄
     * @return {@code Result<Boolean>}
     */
    @Override
    public Result<Boolean> wcsActualAddStock(WcsAddStockCallbackBo bo) {
        log.info("实际添加库存 参数 = {}",bo);

        //获取订单，状态出入库中
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getOrderNo, bo.getOrderNo())
                .eq(BusinOutInPlan::getPlanStatus, PlanStatusEnum.DOING.getValue());

        List<BusinOutInPlan> list = list(wrapper);
        log.info("wcsActualAddStock 结果 = {}", list);
        //如果不是出入库任务，则处理下移库任务
        if (CollectionUtils.isEmpty(list)) {
            return Result.err().msg("没有待处理的任务");
        }

        if (list.stream().anyMatch(x -> Objects.equals(x.getType(), PlanTypeEnum.OUT.getValue()))) {
            log.info("出库不需要调用这个接口，直接返回 = {}", list);
            return Result.ok();
        }

        //入库任务 如果是回库任务走相同的操作
        for (BusinOutInPlan inPlan : list) {
            inPlan.setPlanStatus(PlanStatusEnum.FINISH.getValue());
        }
        Long locationId = list.get(0).getLocationId();
        BaseLocationVo baseLocationVo = baseLocationService.queryLocationById(locationId);
        if (baseLocationVo == null) {
            return Result.err().msg("货位不存在");
        }
        this.updateBatchById(list);

        if (list.stream().anyMatch(x -> Objects.equals(x.getType(), PlanTypeEnum.INV_BACK.getValue()))) {
            log.info("盘点回库，这里不需要处理存库 = {}", list);
            return Result.ok();
        }
        if (list.stream().anyMatch(x -> Objects.equals(x.getType(), PlanTypeEnum.MOVE.getValue()))) {
            log.info("移库操作 这里不需要处理存库 = {}", list);
            return businStockMoveDService.moveTaskFinish(bo.getOrderNo());
        }
        if (list.stream().anyMatch(x -> Objects.equals(x.getType(), PlanTypeEnum.CALL.getValue()))) {
            log.info("呼叫托盘，删除相关信息，并把托盘中的模具存入redis = {}", list);
            //根据当前的单据号，获取托盘编号
            Long orderNo = bo.getOrderNo();
            Result<BusinOutInPlan> businOutInPlanResult = this.wcsGetPlanByOrderNo(orderNo.toString());
            BusinOutInPlan data = businOutInPlanResult.getData();
            log.info("获取空托盘的单据：" + JsonUtil.toJSONString(data));
            Long containerId = data.getContainerId();
            List<SysStockDetail> sysStockDetails = sysStockDetailService.getByContainerId(containerId);
            // 将空托中的模具放入rudis
            log.info("托盘中的模具信息,未过滤之前的:" + sysStockDetails);

            List<SysStockDetail> collect = sysStockDetails.stream().filter(x -> x.getMoldId() != null).collect(Collectors.toList());
            log.info("过滤掉模具编号为空的数据存入Redis中"+collect);

            if(collect.size()>0) {
                redisCache.setCacheList(RedisLockConstants.containerCode + "_" + data.getContainerCode(), sysStockDetails);
            }


            if (sysStockDetails.size() > 0) {
                for (SysStockDetail sysStockDetail : sysStockDetails) {
                    log.info("获取的库存明细Id:"+sysStockDetail.getId());
                    boolean b = sysStockDetailService.removeById(sysStockDetail.getId());
                    if (b){
                        log.info("托盘出库清理成功"+sysStockDetail.getContainerCode());
                        //空托出库时，把货位状态改为空闲
                        baseLocationVo.setLocationStatus(LocationStausEnum.FREE.getValue());
                        baseLocationService.updateById(baseLocationVo);
                    }else {
                        log.info("托盘出库清理失败"+sysStockDetail.getContainerCode());
                    }
                }
            }
            return Result.ok();
        }

        log.info("回库或者入库 需要处理存库 = {}", list);
        StockDetailLocationVo vo = BeanMapperUtils.map(baseLocationVo, StockDetailLocationVo.class);
        sysStockDetailService.addStockByIn(list, vo, true);

        //更新库位状态：计算库位上的模具数量是否满位，半位
        //返回结果为，可容纳模具数量-已放模具数量只差，等于0：满位；不等于6 3 则是半位
        log.info("需要处理的库位信息ID:"+locationId);
        String locationStatus="0";

        Integer locationMoldQty = sysStockDetailService.getLocationMoldQty(locationId);
        if (locationMoldQty ==null){
            //空托盘
            locationStatus=LocationStausEnum.EMPTY.getValue();
        }else {
            locationStatus=LocationStausEnum.FREE.getValue();
            if (locationMoldQty==0){
                locationStatus=LocationStausEnum.FULL.getValue();
            }else if (locationMoldQty !=6 || locationMoldQty !=3){
                locationStatus = LocationStausEnum.NO_FULL.getValue();
            }
        }

        BaseLocation baseLocation = baseLocationService.getById(locationId);
        baseLocation.setLocationStatus(locationStatus);
        baseLocationService.updateById(baseLocation);

        return Result.ok();
    }


    /**
     * 根据订单编号，获取入库或出库的库位信息
     *
     * @param orderNo 订单没有
     * @return {@link Result}<{@link BaseLocation}>
     */
    @Override
    public Result<BaseLocation> wcsGetLocationByOrderNo(String orderNo) {
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getOrderNo, orderNo).last("limit 1");
        BusinOutInPlan one = getOne(wrapper);
        if (one == null) {
            return Result.err().msg("不存在该订单信息");
        }
        BaseLocation location = baseLocationService.getById(one.getLocationId());
        return Result.ok().data(location);
    }

    /**
     * 根据订单编号，查询下计划类型
     *
     * @param orderNo 订单
     * @return {@code Result<BusinOutInPlan>}
     */
    @Override
    public Result<BusinOutInPlan> wcsGetPlanByOrderNo(String orderNo) {
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .eq(BusinOutInPlan::getOrderNo, orderNo).last("limit 1");
        BusinOutInPlan one = getOne(wrapper);
        if (one == null) {
            return Result.err().msg("不存在该订单信息");
        }
        return Result.ok().data(one);
    }

    /**
     * 更新入库计划由0变为1（等待变化执行中）
     *
     * @param orderNo    订单没有
     * @param planStatus
     * @return {@link Result}<{@link Boolean}>
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> updPlanStatus(Long orderNo, String planStatus) {
        LambdaQueryWrapper<BusinOutInPlan> eq = new LambdaQueryWrapper<BusinOutInPlan>().eq(BusinOutInPlan::getOrderNo, orderNo)
                .ne(BusinOutInPlan::getPlanStatus, PlanStatusEnum.FINISH.getValue());
        List<BusinOutInPlan> list = list(eq);
        if (CollectionUtils.isEmpty(list)) {
            log.info("任务已经完成或者不存在该任务,不需更新任务状态");
            return Result.err().msg("任务已经完成或者不存在该任务");
        }
        BusinOutInPlan businOutInPlan = list.get(0);
        if (businOutInPlan.getType().equals(PlanTypeEnum.OUT.getValue())){
            log.info("获取的是出库任务，不需要更新状态");
            return  Result.err().msg("获取的是出库任务，不需要更新状态");
        }

        //取消入库时，如果已给堆垛机下发任务，则删除redis信息
       // redisCache.deleteObject(RedisConstant.redisReqWare + orderNo);
        redisTemplate.delete(RedisConstant.redisContainer+orderNo);
        //堆垛机非自动完成状态后，把redis中的ID删除，代表此任务已经完成
        Object taskInId = redisTemplate.opsForValue().get(RedisConstant.redisTaskIn);
        if (taskInId !=null && String.valueOf(taskInId).equals(orderNo.toString())){
            log.info("手动处理入库时判断redis中的数据是否还存在，存在则删除，删除redis中的出库任务状态记录：",orderNo);
            redisTemplate.delete(RedisConstant.redisTaskIn);
        }

        list.forEach(x -> x.setPlanStatus(planStatus));
        this.updateBatchById(list);
        return Result.ok();
    }

    /**
     * 根据任务编号更新异常信息
     *
     * @param bo 薄
     * @return {@link Result}<{@link Boolean}>
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> updErrMsg(UpdPlanErrorBo bo) {
        LambdaQueryWrapper<BusinOutInPlan> eq = new LambdaQueryWrapper<BusinOutInPlan>().eq(BusinOutInPlan::getOrderNo, bo.getOrderNo());
        List<BusinOutInPlan> list = list(eq);
        if (CollectionUtils.isEmpty(list)) {
            return Result.err().msg("不存在该任务");
        }
        list.forEach(x -> x.setErrorMsg(bo.getErrorMsg()));
        this.updateBatchById(list);
        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> repeatOutStock(Long orderNo) throws InterruptedException {
        LambdaQueryWrapper<BusinOutInPlan> eq = new LambdaQueryWrapper<BusinOutInPlan>().eq(BusinOutInPlan::getOrderNo, orderNo)
                .eq(BusinOutInPlan::getPlanStatus, PlanStatusEnum.WAIT.getValue());
        List<BusinOutInPlan> list = list(eq);
        if (CollectionUtils.isEmpty(list)) {
            return Result.err().msg("任务已经不是等待状态或者不存在该任务");
        }

        // 状态更新也在这个里面了
        Result result = thirdExceptionRequestService.executeException(orderNo.toString());
        if (!Result.isOk(result)) {
            return result;
        }

        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePlanStatusByAgain(String orderNo) {
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>().eq(BusinOutInPlan::getOrderNo, orderNo)
                .eq(BusinOutInPlan::getPlanStatus, PlanStatusEnum.WAIT.getValue());
        BusinOutInPlan plan = new BusinOutInPlan();
        plan.setPlanStatus(PlanStatusEnum.DOING.getValue());
        this.update(plan, wrapper);
    }


    public List<BusinOutInPlan> queryByMoldBarcodeList(List<String> moldBarcodeList,String planType) {
        LambdaQueryWrapper<BusinOutInPlan> wrapper = new LambdaQueryWrapper<BusinOutInPlan>()
                .in(BusinOutInPlan::getMoldBarcode, moldBarcodeList)
                .eq(BusinOutInPlan::getType,planType)
                .eq(BusinOutInPlan::getPlanStatus,PlanStatusEnum.WAIT.getValue());
        return this.list(wrapper);
    }





}
