package com.prolog.cs.biz.outbound.service.impl;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.prolog.cs.basic.item.model.BaseMtlItemMst;
import com.prolog.cs.basic.item.model.vo.ItemMstVO2;
import com.prolog.cs.basic.item.model.vo.WaveItemInfoVO;
import com.prolog.cs.biz.outbound.dao.*;
import com.prolog.cs.biz.outbound.funcflow.WaveDistributeFunc;
import com.prolog.cs.biz.outbound.model.*;
import com.prolog.cs.biz.outbound.model.dto.MoveTaskDODTO;
import com.prolog.cs.biz.outbound.model.dto.RedDTO;
import com.prolog.cs.biz.outbound.model.dto.WaveDistributeDTO;
import com.prolog.cs.biz.outbound.model.entity.*;
import com.prolog.cs.biz.outbound.model.vo.WavePreCalResultVO;
import com.prolog.cs.biz.outbound.resource.InventoryResource;
import com.prolog.cs.biz.outbound.resource.ItemClient;
import com.prolog.cs.biz.outbound.resource.StrategyResource;
import com.prolog.cs.biz.outbound.service.PackService;
import com.prolog.cs.biz.outbound.service.RedService;
import com.prolog.cs.biz.outbound.service.WaveDistributeService;
import com.prolog.cs.biz.outbound.service.WorkStageService;
import com.prolog.cs.biz.outbound.service.strategy.STRATEGY_CODE;
import com.prolog.cs.biz.outbound.service.strategy.StrategySelector;
import com.prolog.cs.biz.outbound.stage.WorkStage;
import com.prolog.cs.biz.outbound.stage.WorkStageAlgorithm;
import com.prolog.cs.biz.outbound.stage.WorkStageContext;
import com.prolog.cs.biz.outbound.utils.ItemUtils;
import com.prolog.cs.biz.outbound.utils.MapBeanUtils;
import com.prolog.cs.biz.outbound.utils.StatusUtil;
import com.prolog.framework.authority.core.dto.UserDTO;
import com.prolog.framework.authority.user.UserTemplate;
import com.prolog.framework.beans.BeanUtils;
import com.prolog.framework.bz.core.model.ModelUtils;
import com.prolog.framework.common.message.RestMessage;
import com.prolog.framework.core.exception.BizException;
import com.prolog.framework.core.exception.PrologException;
import com.prolog.framework.core.restriction.Criteria;
import com.prolog.framework.core.restriction.FieldSelector;
import com.prolog.framework.core.restriction.Restriction;
import com.prolog.framework.core.restriction.Restrictions;
import com.prolog.framework.utils.*;
import com.prolog.function.annotation.Flow;
import com.prolog.function.annotation.FuncImpl;
import com.prolog.upcloud.base.inventory.model.WmsInvBatchNumbers;
import com.prolog.upcloud.base.inventory.vo.*;
import com.prolog.upcloud.base.strategy.model.BusinessControl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 波次下发功能
 *
 * @author chenj
 * Created on 21/03/29.
 */
@Service
public class WaveDistributeServiceImpl implements WaveDistributeService {

    private static Logger logger = LoggerFactory.getLogger(WaveDistributeServiceImpl.class);

    @Autowired
    private BillDPrePositionMapper billDPrePositionMapper;

    @Autowired
    private WaveMapper waveMapper;

    @Autowired
    private WaveBillGroupMapper groupMapper;

    @Autowired
    private WaveBillMapper billMapper;

    @Autowired
    private WaveBillDMapper billDMapper;

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private InventoryResource invClient;

    @Autowired
    private WaveServiceRbMQ waveServiceRbMQ;

    @Autowired
    private OmsDoBillHMapper omsDoBillHMapper;

    @Autowired
    private StrategyResource strategyResource;

    @Autowired
    private SaleRedRecordMapper saleRedRecordMapper;

    @Autowired
    private PackService packService;

    @Autowired
    private RedService redService;

    @Value("${prolog.dao.keyGenerator.snowFlake.dataCenterId:5}")
    private long dataCenterId;

    @Value("${prolog.dao.keyGenerator.snowFlake.machineId:13}")
    private long machineId;
    
    @Autowired
    UserTemplate userTemplate;
    private static final BigDecimal DEFAULT_WEIGHT = BigDecimal.valueOf(33 * 33 * 33);
    private static final BigDecimal DEFAULT_VOLUME = BigDecimal.ZERO;

    @Autowired
    private MoveTaskMapper moveTaskMapper;

    @Autowired
    private StrategySelector strategySelector;

    @Autowired
    private WorkStageService workStageService;

    @Autowired
    private InventoryResource inventoryResource;

    WaveDistributeFunc.validDistribute validDistribute;
    WaveDistributeFunc.structStockWave structStockWave;
    WaveDistributeFunc.queryBillInfoForUpdate queryBillInfoForUpdate;
    WaveDistributeFunc.updateStatus updateStatus;
    WaveDistributeFunc.sendWaveToStock sendWaveToStock;
    WaveDistributeFunc.processBillInfo processBillInfo;
    WaveDistributeFunc.sendInvStockWaveTrial sendInvStockWave;

    @PostConstruct
    public void init() {
        validDistribute = this::validDistribute;
        structStockWave = this::structStockWave;
        queryBillInfoForUpdate = this::queryBillInfoForUpdate;
        updateStatus = this::updateStatus;
        sendWaveToStock = this::sendWaveToStock;
        processBillInfo = this::processBillInfo;
        sendInvStockWave = this::sendInvStockWaveTrial;
    }

    @Override
    public WaveDO getWavesAll(String wavesId) {
        //查询组装波次信息
        WaveDO waveDo = waveMapper.findById(wavesId, WaveDO.class);
        return waveDo;
    }

    /**
     * @param wavesId
     * @param type    0-正常波次 1-特殊波次
     */
    @Override
    @Transactional
    @Flow(name = "波次下发子流程", category = "出库/波次管理/波次下发", index = 0, description = "波次下发子流程")
    public String distributeWave(String wavesId, WaveDistributeDTO vo, String type) {
        // 波次校验是否特殊波次，是否已经下发等
        String billId = null;
        if (null != vo) {
            billId = vo.getBillId();
            List<RedDTO> redDTOs = vo.getRedDTOs();
            if (!StringUtils.isBlank(billId) && null != redDTOs && redDTOs.size()>0) {
                for (int i = 0; i < redDTOs.size(); i++) {
                    //后端转成对应订单ID处理
                    RedDTO redDTO = redDTOs.get(i);
                    redDTO.setBillId(redDTO.getSrcId());
                    redDTO.setBillDId(redDTO.getSrcLineId());
                    redService.makeOmsBillItemRed(redDTOs.get(i));
                }
            }
        }
        WaveDO waveDo = validDistribute.validDistribute(wavesId, billId, type);
        // 查询得到用户VO
        UserDTO userDTO = userTemplate.getUserDTO();
        if (null == userDTO) {
            userDTO = new UserDTO();
            userDTO.setEnterpriseId(waveDo.getEnterpriseId());
            userDTO.setId(String.valueOf(SnowFlakeUtils.getId()));
            userDTO.setUsername("定时任务");
        }
        // 构造波次任务VO
        InvStockWave invStockWave = structStockWave.structStockWave(waveDo);
        invStockWave.setOpUser(userDTO.getUsername());
        invStockWave.setOpUserId(userDTO.getId());
        if (!StringUtils.isBlank(billId)) {
            // 订单下发场景为强制下发,还是订单级成功失败
            invStockWave.setAllowPartFail(false);
        }
        // 查询订单信息并更新
        String warnMsg = queryBillInfoForUpdate.queryBillInfoForUpdate(wavesId, billId, invStockWave);
        // 更新状态
        updateStatus.updateStatus(wavesId,userDTO);
        // 货主转换单不发送波次到库存服务
        if (!waveDo.getType().equals("7")) {
            logger.info("发送波次任务到库存服务,waveId="+waveDo.getId()+ ",InvStockWave="+JSON.toJSONString(invStockWave));
            waveServiceRbMQ.send(invStockWave);
        }
        return warnMsg;
    }

    /**
     * 波次下发校验
     */
    @FuncImpl(name = "波次下发校验", func = WaveDistributeFunc.validDistribute.class, description = "根据波次ID查询wms_wave表是否已经下发，并同时根据状态判断此波次是否是特殊波次")
    private WaveDO validDistribute(String wavesId,String billId, String type) {
        WaveDO waveDo = waveMapper.findById(wavesId, WaveDO.class);
        Criteria crt = Criteria.forClass(WaveBillDO.class);
        Restriction restriction = Restrictions.eq("waveId",wavesId);
        crt.setRestriction(Restrictions.and(restriction));
        List<WaveBillDO> waveBills = billMapper.findByCriteria(crt);
        if (CollectionUtil.isNotEmpty(waveBills) && StringUtils.isBlank(billId)) {
            for (WaveBillDO waveBill : waveBills) {
                if (waveBill.getStatus() != BILL_STATUS.CREATED) {
                    throw new PrologException("波次已经下发不能重复下发" + wavesId);
                }
            }
        }

        if (StringUtils.isBlank(billId)) {
            // 非订单下发场景为非强制下发
            WAVE_STATUS waveStatus = waveDo.getStatus();
            if (waveStatus != WAVE_STATUS.CREATED) {
                logger.error("波次已经下发不能重复下发");
                throw new BizException("波次已经下发不能重复下发" + wavesId);
            }
            if (type.equals("0") && waveDo.getType() != null && !waveDo.getType().equals("1")) {
                logger.error("特殊波次请勿下发");
                throw new BizException("特殊波次请勿下发" + wavesId);
            }
        }
        return waveDo;
    }

    /**
     * 构造波次任务VO
     */
    @FuncImpl(name = "构造波次任务VO", func = WaveDistributeFunc.structStockWave.class, description = "波次任务VO赋值")
    private InvStockWave structStockWave(WaveDO waveDo) {
        InvStockWave invStockWave = new InvStockWave();
        invStockWave.setWaveId(waveDo.getId());
        invStockWave.setWaveType(waveDo.getType());
        invStockWave.setWarehouseId(waveDo.getWarehouseId());
        invStockWave.setEnterpriseId(waveDo.getEnterpriseId());
        return invStockWave;
    }

    /**
     * 查询订单信息并更新
     */
    @FuncImpl(name = "查询订单信息并更新", func = WaveDistributeFunc.queryBillInfoForUpdate.class, description = "根据ID查询波次订单行信息并更新订单状态为已下发")
    private String queryBillInfoForUpdate(String wavesId,String billId, InvStockWave invStockWave) {
        // 订单信息
        List<WaveBillDO> bills = billMapper.getBillsForDistribute(wavesId,billId);
        // 获取所有波次订单的明细数据，用户数据 - by Huanglh 2022-02-14
        List<WaveBillDDO> waveBillDDOList = new ArrayList<>();
        UserDTO userDTO = userTemplate.getUserDTO();
        if (null == userDTO) {
            userDTO = new UserDTO();
            userDTO.setEnterpriseId("");
            userDTO.setId(String.valueOf(SnowFlakeUtils.getId()));
            userDTO.setUsername("定时任务");
        }
        if (ObjectUtils.isNull(userDTO)) {
            throw new BizException("登入用户信息获取失败");
        }
        List<InvStockWaveBill> waveBills = new ArrayList<>();

        // 更新订单状态
        for (WaveBillDO bill : bills) {
            bill.setStatus(BILL_STATUS.DISTRIBUTED);
            billMapper.updateFields(bill, FieldSelector.newInstance().include("status"));
            InvStockWaveBill invStockWaveBill = new InvStockWaveBill();
            invStockWaveBill.setCargoOwnerId(bill.getConsignorId());
            String tempBillId = bill.getId();
            invStockWaveBill.setBillId(tempBillId);
            invStockWaveBill.setBillType(bill.getType());
            invStockWaveBill.setBillGroupId(bill.getBillGroupId());
            invStockWaveBill.setCarrierName(bill.getCarrierName());
            invStockWaveBill.setContactTel(bill.getContactTel());

            waveBills.add(invStockWaveBill);
            // 订单商品信息
            List<WaveBillDDO> billDDOS = billDMapper.findByMap(MapUtils.put("billId", tempBillId).getMap(), WaveBillDDO.class);
            // 商品组装,查询两个服务
            List<String> itemIds = billDDOS.stream().map(WaveBillDDO::getItemId).collect(Collectors.toList());
            // 销售出库、有实物出库、无实物出库
            String[] arr = new String[]{"1","13","14","16","17"};
            if (Arrays.asList(arr).contains(bill.getType())) {
                waveBillDDOList.addAll(billDDOS);
            }
            RestMessage<List<WaveItemInfoVO>> waveItemInfos = itemClient.getWaveItemInfo(itemIds);
            List<WaveItemInfoVO> itemVOs = waveItemInfos.getData();
            List<InvStockWaveItem> invStockWaveItems = new ArrayList<>();
            invStockWaveBill.setItems(invStockWaveItems);
            invStockWaveBill.setBillCode(bill.getCode());
            invStockWaveBill.setSrcCode(bill.getSrcCode());
            invStockWaveBill.setSrcId(bill.getSrcId());
            for (WaveBillDDO billDDO : billDDOS) {
                InvStockWaveItem invStockWaveItem = new InvStockWaveItem();
                WaveItemInfoVO waveItemInfoVO = null;
                for (WaveItemInfoVO itemVo : itemVOs) {
                    if (billDDO.getItemId().equals(itemVo.getItemId())) {
                        waveItemInfoVO = itemVo;
                        break;
                    }
                }
                BigDecimal pieceQty = billDDO.getPieceQty();
                if (waveItemInfoVO != null) {
                    BeanUtils.copyProperties(waveItemInfoVO, billDDO);
                    BeanMap beanMap = BeanMap.create(waveItemInfoVO);
                    billDDO.setItemCategoryCode(waveItemInfoVO.getItemHealthCategory());
                    BigDecimal caseVolume = waveItemInfoVO.getVolume();
                    if (caseVolume == null) {
                        caseVolume = DEFAULT_VOLUME;
                    }
                    BigDecimal caseConversionRate = waveItemInfoVO.getCaseConversionRate();
                    BigDecimal volume = caseVolume.divide(caseConversionRate, 4, RoundingMode.UP);
                    billDDO.setVolume(volume);
                    invStockWaveItem.setCaseConversionRate(waveItemInfoVO.getCaseConversionRate());
                    Integer isCaseOrPiece = waveItemInfoVO.getIsCaseOrPiece();
                    invStockWaveItem.setIsCaseOrPiece(waveItemInfoVO.getIsCaseOrPiece());
                    billDDO.setIsCaseOrPiece(isCaseOrPiece);
                    // @ToDo 双人检查
                    billDDO.setItemHealthIsDouble(waveItemInfoVO.getItemHealthIsDouble());
                    // 体积
                    BigDecimal weight = waveItemInfoVO.getWeight();
                    if (weight != null) {
                        billDDO.setTotalWeight(weight.multiply(pieceQty));
                    }
                    if (volume != null) {
                        billDDO.setTotalVolume(volume.multiply(pieceQty));
                    }
                    beanMap.putAll(MapBeanUtils.convertBean(billDDO));
                    invStockWaveItem.setVolume(caseVolume);
                    invStockWaveItem.setExtData(beanMap);
                } else {
                    logger.error("没有找到对应的商品订单");
                    throw new BizException("没有找到对应的商品订单");
                }
                BigDecimal caseConversionRate = billDDO.getCaseConversionRate();
                int total_pcs = pieceQty.intValue() / caseConversionRate.intValue();
                billDDO.setTotalPcs(total_pcs);
                int oddQty = pieceQty.intValue() % caseConversionRate.intValue();
                billDDO.setOddPieceQty(BigDecimal.valueOf(oddQty));
                invStockWaveItems.add(invStockWaveItem);
                invStockWaveItem.setLineBillId(billDDO.getId());
                invStockWaveItem.setItemHealthCategory(waveItemInfoVO.getItemHealthCategory());
                invStockWaveItem.setManufacturerLotNum(billDDO.getManufacturerLotNum());
                invStockWaveItem.setItemId(billDDO.getItemId());
                invStockWaveItem.setItemName(waveItemInfoVO.getItemName());
                invStockWaveItem.setSrcLineId(billDDO.getSrcLineId());
                // @Todo delete this
                invStockWaveItem.setPickRule("FIFO");
                invStockWaveItem.setQuantity(pieceQty);
                invStockWaveItem.setLineBillId(billDDO.getId());
                invStockWaveItem.setStockStatus(billDDO.getStockStatus());
                invStockWaveItem.setIsWholeQty(billDDO.getIsWholeQty());
                assignLocators(billDDO, invStockWaveItem);

                String mergedId = bill.getSrcId();
                billDDO.setMergedBillId(mergedId);
                invStockWaveItem.setMergedBillId(mergedId);
                billDMapper.update(billDDO);
            }
            // 最后更新信息
            updateBillInfo(bill);
        }
        invStockWave.setBills(waveBills);
        // return alertNear(bills.get(0).getWarehouseId(),bills.get(0).getConsignorId(),waveBillDDOList);
        return null;
    }

    public String alertNear(String warehouseId, String cargoOwnerId, List<WaveBillDDO> waveBillDDOList) {
        logger.info("alertNear,warehouseId="+warehouseId+",cargoOwnerId="+cargoOwnerId+",waveBillDDOList="+JSON.toJSONString(waveBillDDOList));
        if (waveBillDDOList==null || waveBillDDOList.size()==0) {
            return "";
        }
        String resMsg = null;
        // 获取货主的 近效期天数 业务配置
        Integer nearDays=90,outBoundNearCheck=0;
        RestMessage<Map<String, BusinessControl>> restMessage = strategyResource.getControlByCodes(new String[]
                        {"CARGO_OWNER__NEAR_EXPIRY_DATE_DAY", "OUTBOUND_NEAR_CHECK"}, warehouseId, null,
                userTemplate.getUserDTO().getEnterpriseId(), null);
        if (restMessage.isSuccess() && restMessage.getData() != null) {
            Map<String, BusinessControl> map = restMessage.getData();
            if (map.containsKey("CARGO_OWNER__NEAR_EXPIRY_DATE_DAY"))
                nearDays = Integer.valueOf(map.get("CARGO_OWNER__NEAR_EXPIRY_DATE_DAY").getRegValue());
            if (map.containsKey("OUTBOUND_NEAR_CHECK"))
                outBoundNearCheck = Integer.valueOf(map.get("OUTBOUND_NEAR_CHECK").getRegValue());
        }
        if (0 == outBoundNearCheck) {
            return resMsg;
        }
        List<WmsInvStockQtyVO> resultData = null;
        try {
            // 库存校验近效期异常，不能影响主流程
            List<WmsInvStockQtyQueryVO> list=new ArrayList();
            for (int i = 0; i < waveBillDDOList.size(); i++) {
                WaveBillDDO waveBillDDO=waveBillDDOList.get(i);
                if(StringUtils.isBlank(waveBillDDO.getManufacturerLotNum())){
                    continue;
                }
                WmsInvStockQtyQueryVO queryVO = new WmsInvStockQtyQueryVO();
                queryVO.setEnterpriseId(waveBillDDO.getEnterpriseId());
                queryVO.setWarehouseId(warehouseId);
                queryVO.setCargoOwnerId(cargoOwnerId);
                queryVO.setManufacturerLotNum(waveBillDDO.getManufacturerLotNum());
                queryVO.setItemId(waveBillDDO.getItemId());
                queryVO.setStatus(waveBillDDO.getStockStatus());
                list.add(queryVO);
            }
            if (list.size()==0) {
                return resMsg;
            }
            // 请求库存批次信息
            RestMessage<List<WmsInvStockQtyVO>> result = inventoryResource.getAvailableQuantityList(list);
            if(result==null || !result.isSuccess() || result.getData()==null || result.getData().size()==0){
                logger.error("校验近效期调用库存服务接口异常,alertNear,warehouseId="+warehouseId+",cargoOwnerId="+cargoOwnerId+",waveBillDDOList="+JSON.toJSONString(waveBillDDOList));
                return "校验近效期调用库存服务接口异常";
            }
            resultData = result.getData();
            logger.info("alertNear,warehouseId="+warehouseId+",cargoOwnerId="+cargoOwnerId+",waveBillDDOList="+JSON.toJSONString(waveBillDDOList));
        } catch (Exception e) {
            logger.error("校验近效期调用库存服务接口异常,warehouseId="+warehouseId+",cargoOwnerId="+cargoOwnerId+",waveBillDDOList="+JSON.toJSONString(waveBillDDOList),e);
            return "校验近效期调用库存服务接口异常";
        }
        Set<String> itemIdSet=new HashSet<>();
        Map<String, List<WmsInvStockQtyVO>> groupByManufacturerLotNum=new HashMap<>();
        for (int i = 0; i < resultData.size(); i++) {
            WmsInvStockQtyVO invStockQtyVO=resultData.get(i);
            BigDecimal qty = invStockQtyVO.getAvailableQuantity();
            if (invStockQtyVO.getBatchExpiredDate()==null || qty ==null || qty.compareTo(BigDecimal.ZERO)<0) {
                continue;
            }
            itemIdSet.add(invStockQtyVO.getItemId());
            String key = invStockQtyVO.getItemId() + "_" + invStockQtyVO.getManufacturerLotNum();
            if (groupByManufacturerLotNum.containsKey(key)) {
                groupByManufacturerLotNum.get(key).add(invStockQtyVO);
            } else {
                List<WmsInvStockQtyVO> list=new ArrayList<>();
                list.add(invStockQtyVO);
                groupByManufacturerLotNum.put(key,list);
            }
        }
        // 根据id查询商品批次信息，用来获取有效期
        RestMessage<List<BaseMtlItemMst>> itemResult =null;
        if (itemIdSet!=null && itemIdSet.size()>0 && !StringUtils.isBlank(itemIdSet.iterator().next())) {
            itemResult=itemClient.getListByIds(itemIdSet);
        }

        Map<String ,WmsInvStockQtyVO> map=new HashMap<>();
        groupByManufacturerLotNum.forEach((key, v) -> {
            if (v!=null && v.size()>0) {
                map.put(key,v.stream().min(Comparator.comparing(WmsInvStockQtyVO::getBatchExpiredDate)).get());
            }
        });
        BigDecimal shelfLifeDays = BigDecimal.ZERO;
        List<String> msgList = new ArrayList<>();
        for (Map.Entry<String, WmsInvStockQtyVO> entry : map.entrySet()) {
            WmsInvStockQtyVO v = entry.getValue();
            Date expiredDate = v.getBatchExpiredDate();
            int days = (int) ((expiredDate.getTime() - (new Date()).getTime()) / (1000*3600*24));
            String itemName = v.getItemId();
            String manufacturerLotNum = v.getManufacturerLotNum();
            if (itemResult!=null && itemResult.getData()!=null && itemResult.getData().size()!=0) {
                BaseMtlItemMst baseMtlItemMst=itemResult.getData().stream().
                        filter(t->t.getItemId().equals(v.getItemId())).findFirst().orElse(null);
                if (baseMtlItemMst!=null) {
                    itemName=v.getItemName();
                }
                if (baseMtlItemMst!=null && baseMtlItemMst.getBaseMtlItemWms()!=null &&
                        baseMtlItemMst.getBaseMtlItemWms().getShelfLifeDays()!=null) {
                    shelfLifeDays=baseMtlItemMst.getBaseMtlItemWms().getShelfLifeDays();
                }
            }
            if (days<0) {
                logger.error(itemName +"，批号：" + manufacturerLotNum + "过效期");
                throw new BizException(itemName +"，批号：" + manufacturerLotNum + "过效期");
            }
            // 比较 当前时间 + 近效天数 和 过期时间，在 过期时间 之后的，则需要 近效期提示，不指定批号库存拦截了，不会出近效期的
            if (days<nearDays) {
                msgList.add(itemName +"，批号：" + manufacturerLotNum + "小于近效期");
            }

            if (days<shelfLifeDays.intValue()) {
                msgList.add(itemName + "，批号：" + manufacturerLotNum + "小于质保期");
            }
        }
        if (msgList.size() > 0) {
            resMsg = String.join("，", msgList);
        }
        logger.info("alertNear,resultData="+JSON.toJSONString(resultData)+",resMsg:"+resMsg);
        return resMsg;
    }

    private InvStockWave spellWaveInventoryVO(String waveId) {
        // 订单信息
        List<WaveBillDO> bills = billMapper.getBillsForDistribute(waveId,null);
        // 获取所有波次订单的明细数据，用户数据 - by Huanglh 2022-02-14
        List<WaveBillDDO> wBillDList = new ArrayList<>();
        InvStockWave invStockWave = new InvStockWave();
        invStockWave.setWaveId(waveId);
        invStockWave.setWaveType("1");
        invStockWave.setWarehouseId(bills.get(0).getWarehouseId());
        invStockWave.setEnterpriseId(bills.get(0).getEnterpriseId());
        List<InvStockWaveBill> waveBills=new ArrayList<>();
        invStockWave.setBills(waveBills);
        // 更新订单状态
        for (WaveBillDO bill : bills) {
            InvStockWaveBill invStockWaveBill = new InvStockWaveBill();
            invStockWaveBill.setCargoOwnerId(bill.getConsignorId());
            String tempBillId = bill.getId();
            invStockWaveBill.setBillId(tempBillId);
            invStockWaveBill.setBillType(bill.getType());
            invStockWaveBill.setBillGroupId(bill.getBillGroupId());
            waveBills.add(invStockWaveBill);
            // 订单商品信息
            List<WaveBillDDO> billDDOS = billDMapper.findByMap(MapUtils.put("billId", tempBillId).getMap(), WaveBillDDO.class);
            // 获取所有波次订单的明细数据 - by Huanglh 2022-02-14
            wBillDList.addAll(billDDOS);
            // 商品组装,查询两个服务
            List<String> itemIds = billDDOS.stream().map(WaveBillDDO::getItemId).collect(Collectors.toList());
            RestMessage<List<WaveItemInfoVO>> waveItemInfos = itemClient.getWaveItemInfo(itemIds);
            List<WaveItemInfoVO> itemVOs = waveItemInfos.getData();
            List<InvStockWaveItem> invStockWaveItems = new ArrayList<>();
            invStockWaveBill.setItems(invStockWaveItems);
            invStockWaveBill.setBillCode(bill.getCode());
            invStockWaveBill.setSrcCode(bill.getSrcCode());
            invStockWaveBill.setSrcId(bill.getSrcId());
            for (WaveBillDDO billDDO : billDDOS) {
                InvStockWaveItem invStockWaveItem = new InvStockWaveItem();
                WaveItemInfoVO waveItemInfoVO = null;
                for (WaveItemInfoVO itemVo : itemVOs) {
                    if (billDDO.getItemId().equals(itemVo.getItemId())) {
                        waveItemInfoVO = itemVo;
                        break;
                    }
                }
                BigDecimal pieceQty = billDDO.getPieceQty();
                if (waveItemInfoVO != null) {
                    BeanUtils.copyProperties(waveItemInfoVO, billDDO);
                    BeanMap beanMap = BeanMap.create(waveItemInfoVO);
                    billDDO.setItemCategoryCode(waveItemInfoVO.getItemHealthCategory());
                    BigDecimal caseVolume = waveItemInfoVO.getVolume();
                    if (caseVolume == null) {
                        caseVolume = DEFAULT_VOLUME;
                    }
                    BigDecimal caseConversionRate = waveItemInfoVO.getCaseConversionRate();
                    BigDecimal volume = caseVolume.divide(caseConversionRate, 4, RoundingMode.UP);
                    billDDO.setVolume(volume);
                    invStockWaveItem.setCaseConversionRate(waveItemInfoVO.getCaseConversionRate());
                    Integer isCaseOrPiece = waveItemInfoVO.getIsCaseOrPiece();
                    invStockWaveItem.setIsCaseOrPiece(waveItemInfoVO.getIsCaseOrPiece());
                    billDDO.setIsCaseOrPiece(isCaseOrPiece);
                    // @ToDo 双人检查
                    billDDO.setItemHealthIsDouble(waveItemInfoVO.getItemHealthIsDouble());
                    // 体积
                    BigDecimal weight = waveItemInfoVO.getWeight();
                    if (weight != null) {
                        billDDO.setTotalWeight(weight.multiply(pieceQty));
                    }
                    if (volume != null) {
                        billDDO.setTotalVolume(volume.multiply(pieceQty));
                    }
                    beanMap.putAll(MapBeanUtils.convertBean(billDDO));
                    invStockWaveItem.setVolume(caseVolume);
                    invStockWaveItem.setExtData(beanMap);
                } else {
                    logger.error("没有找到对应的商品订单");
                    throw new BizException("没有找到对应的商品订单");
                }
                BigDecimal caseConversionRate = billDDO.getCaseConversionRate();
                int total_pcs = pieceQty.intValue() / caseConversionRate.intValue();
                billDDO.setTotalPcs(total_pcs);
                int oddQty = pieceQty.intValue() % caseConversionRate.intValue();
                billDDO.setOddPieceQty(BigDecimal.valueOf(oddQty));
                invStockWaveItem.setLineBillId(billDDO.getId());
                invStockWaveItem.setItemHealthCategory(waveItemInfoVO.getItemHealthCategory());
                invStockWaveItem.setManufacturerLotNum(billDDO.getManufacturerLotNum());
                invStockWaveItem.setItemId(billDDO.getItemId());
                invStockWaveItem.setItemName(waveItemInfoVO.getItemName());
                invStockWaveItem.setSrcLineId(billDDO.getSrcLineId());
                // @Todo delete this
                invStockWaveItem.setPickRule("FIFO");
                invStockWaveItem.setQuantity(pieceQty);
                invStockWaveItem.setLineBillId(billDDO.getId());
                invStockWaveItem.setStockStatus(billDDO.getStockStatus());
                assignLocators(billDDO, invStockWaveItem);
                invStockWaveItems.add(invStockWaveItem);
            }
        }
        return invStockWave;
    }


    /**
     * 更新状态
     */
    @FuncImpl(name = "更新状态", func = WaveDistributeFunc.updateStatus.class, description = "根据WavesId更新wave_bill表状态为已下发")
    private void updateStatus(String wavesId,UserDTO userDTO) {
        Map<String, Object> status = MapUtils.put("status", WAVE_STATUS.DISTRIBUTED).getMap();
        StatusUtil.updateWaveEndStatus(wavesId, WAVE_STATUS.CREATED, userDTO);
        StatusUtil.updateWaveStartStatus(wavesId, WAVE_STATUS.DISTRIBUTED, userDTO);
        waveMapper.updateMapById(wavesId, status, WaveDO.class);
    }

    /**
     * 构造波次任务VO
     */
    @FuncImpl(name = "发送波次任务到库存服务", func = WaveDistributeFunc.sendWaveToStock.class, description = "发送invStockWave波次任务到库存服务")
    private void sendWaveToStock(WaveDO waveDo,InvStockWave invStockWave,UserDTO userDTO) {
        invStockWave.setOpUser(userDTO.getUsername());
        invStockWave.setOpUserId(userDTO.getId());
        invStockWave.setWaveType(waveDo.getType());
        // 货主转换单不发送波次到库存服务
        if (!waveDo.getType().equals("7")) {
            logger.info("发送波次任务到库存服务,waveId="+waveDo.getId()+ ",InvStockWave="+JSON.toJSONString(invStockWave));
            waveServiceRbMQ.send(invStockWave);
        }
    }

    /**
     * @param wavesId
     * @param type    0-正常波次 1-特殊波次
     */
    @Override
    @Flow(name = "任务试算子流程", category = "出库/波次管理/任务试算", index = 0, description = "任务试算子流程")
    public List<WavePreCalResultVO> waveTaskTrial(String wavesId, String type,List<String> errorMsgList) {
        // 波次校验是否特殊波次，是否已经下发等
        WaveDO waveDo = validDistribute.validDistribute(wavesId,null,type);
        // 查询得到用户VO
        UserDTO userDTO = userTemplate.getUserDTO();
        // 构造试算任务VO
        InvStockWave invStockWave = structStockWave.structStockWave(waveDo);
        // 查询订单信息
        List<InvStockWaveBill> waveBills = new ArrayList<>();
        processBillInfo.processBillInfo(wavesId,waveBills);
        invStockWave.setBills(waveBills);
        // 发送波次任务到库存服务
        return sendInvStockWave.sendInvStockWaveTrial(waveDo,invStockWave,userDTO,errorMsgList);
    }

    /**
     * 为任务试算处理单据信息
     */
    @FuncImpl(name = "为任务试算处理单据信息", func = WaveDistributeFunc.processBillInfo.class, description = "根据ID为任务试算查询波次订单行信息")
    private void processBillInfo(String wavesId, List<InvStockWaveBill> waveBills) {
        // 订单信息
        List<WaveBillDO> bills = billMapper.getBillsForDistribute(wavesId,null);
        // 更新订单状态
        for (WaveBillDO bill : bills) {
            bill.setStatus(BILL_STATUS.DISTRIBUTED);
            // billMapper.updateFields(bill, FieldSelector.newInstance().include("status"));
            InvStockWaveBill invStockWaveBill = new InvStockWaveBill();
            invStockWaveBill.setCargoOwnerId(bill.getConsignorId());
            String tempBillId = bill.getId();
            invStockWaveBill.setBillId(tempBillId);
            invStockWaveBill.setBillGroupId(bill.getBillGroupId());
            waveBills.add(invStockWaveBill);
            // 订单商品信息
            List<WaveBillDDO> billDDOS = billDMapper.findByMap(MapUtils.put("billId", tempBillId).getMap(), WaveBillDDO.class);
            // 商品组装,查询两个服务
            List<String> itemIds = billDDOS.stream().map(WaveBillDDO::getItemId).collect(Collectors.toList());
            RestMessage<List<WaveItemInfoVO>> waveItemInfos = itemClient.getWaveItemInfo(itemIds);
            List<WaveItemInfoVO> itemVOs = waveItemInfos.getData();
            List<InvStockWaveItem> invStockWaveItems = new ArrayList<>();
            invStockWaveBill.setItems(invStockWaveItems);
            invStockWaveBill.setBillCode(bill.getCode());
            invStockWaveBill.setSrcCode(bill.getSrcCode());
            invStockWaveBill.setSrcId(bill.getSrcId());
            for (WaveBillDDO billDDO : billDDOS) {
                InvStockWaveItem invStockWaveItem = new InvStockWaveItem();
                WaveItemInfoVO waveItemInfoVO = null;
                for (WaveItemInfoVO itemVo : itemVOs) {
                    if (billDDO.getItemId().equals(itemVo.getItemId())) {
                        waveItemInfoVO = itemVo;
                        break;
                    }
                }
                BigDecimal pieceQty = billDDO.getPieceQty();
                if (waveItemInfoVO != null) {
                    BeanUtils.copyProperties(waveItemInfoVO, billDDO);
                    BeanMap beanMap = BeanMap.create(waveItemInfoVO);
                    billDDO.setItemCategoryCode(waveItemInfoVO.getItemHealthCategory());
                    BigDecimal caseVolume = waveItemInfoVO.getVolume();
                    if (caseVolume == null) {
                        caseVolume = DEFAULT_VOLUME;
                    }
                    BigDecimal caseConversionRate = waveItemInfoVO.getCaseConversionRate();
                    BigDecimal volume = caseVolume.divide(caseConversionRate, 4, RoundingMode.UP);
                    billDDO.setVolume(volume);
                    invStockWaveItem.setCaseConversionRate(waveItemInfoVO.getCaseConversionRate());
                    Integer isCaseOrPiece = waveItemInfoVO.getIsCaseOrPiece();
                    invStockWaveItem.setIsCaseOrPiece(waveItemInfoVO.getIsCaseOrPiece());
                    billDDO.setIsCaseOrPiece(isCaseOrPiece);
                    // @ToDo 双人检查
                    billDDO.setItemHealthIsDouble(waveItemInfoVO.getItemHealthIsDouble());
                    // 体积
                    BigDecimal weight = waveItemInfoVO.getWeight();
                    if (weight != null) {
                        billDDO.setTotalWeight(weight.multiply(pieceQty));
                    }
                    if (volume != null) {
                        billDDO.setTotalVolume(volume.multiply(pieceQty));
                    }
                    beanMap.putAll(MapBeanUtils.convertBean(billDDO));
                    invStockWaveItem.setVolume(caseVolume);
                    invStockWaveItem.setExtData(beanMap);
                } else {
                    logger.error("没有找到对应的商品订单");
                    throw new BizException("没有找到对应的商品订单");
                }
                BigDecimal caseConversionRate = billDDO.getCaseConversionRate();
                int total_pcs = pieceQty.intValue() / caseConversionRate.intValue();
                billDDO.setTotalPcs(total_pcs);
                int oddQty = pieceQty.intValue() % caseConversionRate.intValue();
                billDDO.setOddPieceQty(BigDecimal.valueOf(oddQty));
                invStockWaveItems.add(invStockWaveItem);
                invStockWaveItem.setLineBillId(billDDO.getId());
                invStockWaveItem.setItemHealthCategory(waveItemInfoVO.getItemHealthCategory());
                invStockWaveItem.setManufacturerLotNum(billDDO.getManufacturerLotNum());
                invStockWaveItem.setItemId(billDDO.getItemId());
                invStockWaveItem.setItemName(waveItemInfoVO.getItemName());
                invStockWaveItem.setSrcLineId(billDDO.getSrcLineId());
                // @Todo delete this
                invStockWaveItem.setPickRule("FIFO");
                invStockWaveItem.setQuantity(pieceQty);
                invStockWaveItem.setLineBillId(billDDO.getId());
                invStockWaveItem.setStockStatus(billDDO.getStockStatus());
                assignLocators(billDDO, invStockWaveItem);
            }
            // 最后更新信息
            updateBillInfo(bill);
        }
    }

    /**
     * 发送任务试算VO到库存服务
     */
    @FuncImpl(name = "发送任务试算VO到库存服务", func = WaveDistributeFunc.sendInvStockWaveTrial.class, description = "发送任务试算VO到库存服务")
    private List<WavePreCalResultVO> sendInvStockWaveTrial(WaveDO waveDo, InvStockWave invStockWave, UserDTO userDTO,List<String> errorMsgList) {
        invStockWave.setOpUser(userDTO.getUsername());
        invStockWave.setOpUserId(userDTO.getId());
        invStockWave.setWaveType("0");
        List<WavePreCalResultVO> rsList = new ArrayList<>();
        // 货主转换单不发送波次到库存服务
        StockWaveResultVO waveResult=null;
        logger.info("发送任务试算VO到库存服务");
        try {
            logger.info("发送任务试算VO到库存服务参数：{}", JsonUtils.toString(invStockWave));
            RestMessage<StockWaveResultVO> result = invClient.tryWaveTask(invStockWave);
            waveResult=result.getData();
            if(waveResult!=null && waveResult.getDetails()!=null && waveResult.getDetails().size()>0){
                errorMsgList.addAll(waveResult.getDetails().stream().filter(t->!t.isSuccess()).
                        map(t->t.getErrorMsg()).collect(Collectors.toList()));
            }
            logger.info("任务试算返回结果：{}",JSON.toJSONString(result));
        } catch (Exception e) {
            logger.error("处理库存服务返回的波次信息处理失败,失败原因为", e);
            throw new BizException("没有找到对应的商品订单");
        }
        try {
            workStageService.tryHandle(waveResult,rsList);
        } catch (Exception e) {
            logger.info("试算完成waveDo:"+JSON.toJSONString(waveDo));
        }
        if(rsList==null || rsList.size()==0){
            logger.error("试算失败waveDo:"+JSON.toJSONString(waveDo));
        }
        return rsList;
    }

    private WaveDO validWaveHandle(StockWaveResultVO waveResult) {
        String waveId = waveResult.getWaveId();
        logger.error("开始处理波次结果信息,wave is :{}", JSON.toJSONString(waveResult));
        WaveDO waveDO = waveMapper.findById(waveId, WaveDO.class);
        if (waveDO == null) {
            logger.error("找不到对应的波次");
            return null;
        }
        // 更新wave状态为库存已经预占,则时插入状态码信息到状态码表
        List<StockWaveResultDetailVO> billsResults = waveResult.getDetails();
        if (billsResults.isEmpty()) {
            logger.warn("该波次所有订单预占失败");
            return null;
        }
        return waveDO;
    }

    public void processTaskNum(StockWaveResultVO waveResult, List<StockWaveResultDetailVO> successBillsResults,Map<String,Object> rsMap) {
        List<WmsMoveTaskRef> wmsMoveTaskRefList=new ArrayList<>();
        List<MoveTaskDODTO> moveTaskDODTOList=new ArrayList<>();
        Date gmtCreate = new Date();
        for (int i = 0; i < successBillsResults.size(); i++) {
            StockWaveResultDetailVO stockWaveResultDetailVO= successBillsResults.get(i);
            List<StockWaveItemDetail> itemDetails = stockWaveResultDetailVO.getItemDetails();
            if(itemDetails!=null && itemDetails.size()>0){
                for (int j = 0; j < itemDetails.size(); j++) {
                    StockWaveItemDetail stockWaveItemDetail = itemDetails.get(j);
                    List<StockWaveRefBill> refReplenishes = stockWaveItemDetail.getRefReplenishes();
                    if(refReplenishes!=null && refReplenishes.size()>0){
                        for (int k = 0; k < refReplenishes.size(); k++) {
                            StockWaveRefBill refBill= refReplenishes.get(k);
                            WmsMoveTaskRef wmsMoveTaskRef =new WmsMoveTaskRef();
                            org.springframework.beans.BeanUtils.copyProperties(refBill, wmsMoveTaskRef);
                            String moveTaskId = String.valueOf(SnowFlakeUtils.getId(dataCenterId,machineId));
                            wmsMoveTaskRef.setId(moveTaskId);
                            wmsMoveTaskRef.setGmtCreate(gmtCreate);
                            WaveBillDO bill = billMapper.findById(refBill.getBillId(), WaveBillDO.class);
                            if(bill!=null){
                                wmsMoveTaskRef.setWaveId(bill.getWaveId());
                                wmsMoveTaskRef.setSrcCode(bill.getSrcCode());
                            }
                            wmsMoveTaskRefList.add(wmsMoveTaskRef);
                        }
                    }
                    List<StockWaveReplenishTaskVO> replenishTaskList = stockWaveItemDetail.getReplenishTaskList();
                    if(replenishTaskList!=null && replenishTaskList.size()>0){
                        for (int k = 0; k < replenishTaskList.size(); k++) {
                            StockWaveReplenishTaskVO stockWaveReplenishTaskVO= replenishTaskList.get(k);
                            MoveTaskDODTO moveTaskDODTO=new MoveTaskDODTO();
                            org.springframework.beans.BeanUtils.copyProperties(stockWaveReplenishTaskVO,moveTaskDODTO);
                            moveTaskDODTO.setItemId(stockWaveItemDetail.getItemId());
                            moveTaskDODTO.setCargoOwnerId(stockWaveResultDetailVO.getCargoOwnerId());
                            moveTaskDODTO.setBldgId(stockWaveReplenishTaskVO.getBuildingId());
                            moveTaskDODTO.setItemName(stockWaveItemDetail.getItemName());
                            moveTaskDODTO.setMoveType("4");
                            moveTaskDODTO.setConversionRate(stockWaveItemDetail.getCaseConversionRate());
                            moveTaskDODTO.setEnterpriseId(waveResult.getEnterpriseId());
                            moveTaskDODTO.setWarehouseId(waveResult.getWarehouseId());
                            moveTaskDODTO.setSrcFloor(stockWaveReplenishTaskVO.getSrcFloorNum());
                            moveTaskDODTO.setSrcWarehouseAreaNum(stockWaveReplenishTaskVO.getSrcWarehouseAreaCode());
                            moveTaskDODTO.setSrcWarehouseLocatorNum(stockWaveReplenishTaskVO.getSrcWarehouseAreaCode());
                            moveTaskDODTO.setExpiredDate(stockWaveReplenishTaskVO.getBatchExpiredDate());
                            moveTaskDODTO.setProductionDate(stockWaveReplenishTaskVO.getBatchProductionDate());
                            moveTaskDODTO.setQty(stockWaveReplenishTaskVO.getPieceQuantity());
                            List<String> itemIds = new ArrayList<String>();
                            itemIds.add(stockWaveItemDetail.getItemId());
                            RestMessage<List<WaveItemInfoVO>> waveItemInfos = itemClient.getWaveItemInfo(itemIds);
                            moveTaskDODTO.setItemCode(waveItemInfos.getData().get(0).getItemCode());
                            moveTaskDODTOList.add(moveTaskDODTO);
                        }
                    }
                }
            }
        }
        getTaskNum(moveTaskDODTOList,wmsMoveTaskRefList,rsMap);
    }

    public void getTaskNum(List<MoveTaskDODTO> moveTaskDODTOS,List<WmsMoveTaskRef> wmsMoveTaskRefList,Map<String,Object> rsMap) {
        logger.info("addMoveTask,moveTaskDODTOS:{},wmsMoveTaskRefList:{}", JSON.toJSONString(moveTaskDODTOS),JSON.toJSONString(wmsMoveTaskRefList));
        UserDTO userDTO = userTemplate.getUserDTO();
        // 订单集合
        List<WaveBillDO> waveBillDOList = new ArrayList<>();
        // 订单明细行集合
        List<WaveBillDDO> waveBillDDOList = new ArrayList<>();
        // 补货任务集合
        List<MoveTaskDO> moveTaskDOList = new ArrayList<>();
        // 波次库存预占集合
        List<WaveStorageDO> waveStorageDOList = new ArrayList<>();
        // 捡选任务头集合
        List<PickTaskH> pickTaskHList = new ArrayList<>();
        // 捡选任务行集合
        List<PickTaskD> pickTaskDList = new ArrayList<>();
        for (MoveTaskDODTO moveTaskDODTO : moveTaskDODTOS) {
            //创建订单信息
            WaveBillDO billDO = new WaveBillDO();
            String billId = String.valueOf(SnowFlakeUtils.getId(dataCenterId,machineId));
            billDO.setConsignorId(moveTaskDODTO.getCargoOwnerId());
            billDO.setId(billId);
            billDO.setIsMerged(0);
            billDO.setSrcCode(billId);
            billDO.setCode(billId);
            billDO.setBillGroupId(billId);
            String enterpriseId = userDTO==null?moveTaskDODTO.getEnterpriseId():userDTO.getEnterpriseId();
            String warehouseId = moveTaskDODTO.getWarehouseId();
            billDO.setWarehouseId(warehouseId);
            //@TODO 补货出库单
            billDO.setType("20"); //补货出库单
            if(userDTO!=null){
                ModelUtils.setBaseInfo(billDO, ModelUtils.OpType.INSERT, userDTO);
            }
            waveBillDOList.add(billDO);

            //创建订单明细信息
            WaveBillDDO billDDO = new WaveBillDDO();
            billDDO.setBillId(billId);
            billDDO.setManufacturerLotNum(moveTaskDODTO.getManufacturerLotNum());
            billDDO.setCaseConversionRate(moveTaskDODTO.getConversionRate());
            billDDO.setIsCaseOrPiece(moveTaskDODTO.getIsCaseOrPiece());
            String billDId = String.valueOf(SnowFlakeUtils.getId(dataCenterId,machineId));
            billDDO.setId(billDId);
            billDDO.setItemId(moveTaskDODTO.getItemId());
            billDDO.setVolume(moveTaskDODTO.getVolume());
            ItemMstVO2 itemsInfo = ItemUtils.getItemsInfo(billDDO.getItemId(),enterpriseId);
            billDDO.setManufacturer(itemsInfo.getManufacturerName());
            billDDO.setItemSpecs(itemsInfo.getSpecification());
            ItemMstVO2 itemsInfo1 = ItemUtils.getItemsInfo(moveTaskDODTO.getItemId(),enterpriseId);
            billDDO.setManufacturer(itemsInfo1.getManufacturerName());
            billDDO.setItemSpecs(itemsInfo1.getSpecification());
            billDDO.setIsCaseOrPiece(itemsInfo.getIsCaseOrPiece());
            if(userDTO!=null){
                ModelUtils.setBaseInfo(billDO, ModelUtils.OpType.INSERT, userDTO);
            }
            billDDO.setItemCode(moveTaskDODTO.getItemCode());
            billDDO.setItemName(moveTaskDODTO.getItemName());
            waveBillDDOList.add(billDDO);

            //创建补货表
            MoveTaskDO moveTaskDO = new MoveTaskDO();
            org.springframework.beans.BeanUtils.copyProperties(moveTaskDODTO, moveTaskDO);
            moveTaskDO.setStatus("0");
            if(userDTO!=null){
                ModelUtils.setBaseInfo(moveTaskDO, ModelUtils.OpType.INSERT, userDTO);
            }
            String moveTaskId = String.valueOf(SnowFlakeUtils.getId(dataCenterId,machineId));
            moveTaskDO.setMoveTaskId(moveTaskId);
            moveTaskDO.setBatchNum(moveTaskDODTO.getBatchNum());
            moveTaskDO.setBillId(billId);
            moveTaskDO.setBillDId(billDId);
            moveTaskDO.setMoveType(moveTaskDODTO.getMoveType());
            moveTaskDOList.add(moveTaskDO);

            //创建库存预占表
            WaveStorageDO waveStorageDO = new WaveStorageDO();
            String bldgName = moveTaskDO.getBldgName();
            waveStorageDO.setId(String.valueOf(SnowFlakeUtils.getId()));
            waveStorageDO.setBldgName(bldgName);
            waveStorageDO.setBldgId(moveTaskDODTO.getBldgId());
            waveStorageDO.setBatchId(moveTaskDODTO.getBatchId());
            waveStorageDO.setBatchNum(moveTaskDODTO.getBatchNum());
            waveStorageDO.setFloorNum(moveTaskDODTO.getSrcFloor());
            waveStorageDO.setProductionDate(moveTaskDODTO.getProductionDate());
            waveStorageDO.setExpiredDate(moveTaskDODTO.getExpiredDate());
            String manufacturerLotNum = moveTaskDODTO.getManufacturerLotNum();
            waveStorageDO.setManufactureLotNum(manufacturerLotNum);
            waveStorageDO.setType(1);
            waveStorageDO.setWarehouseId(warehouseId);
            String srcWarehouseLocatorNum = moveTaskDODTO.getSrcWarehouseLocatorNum();
            waveStorageDO.setLocatorCode(srcWarehouseLocatorNum);
            waveStorageDO.setLocatorId(moveTaskDODTO.getSrcWarehouseLocatorId());
            waveStorageDO.setLocatorType(moveTaskDODTO.getSrcWarehouseLocatorType());
            waveStorageDO.setAreaCategory(moveTaskDODTO.getSrcWarehouseAreaCategory());
            waveStorageDO.setWarehouseAreaCode(moveTaskDODTO.getSrcWarehouseAreaNum());
            waveStorageDO.setExpiredDateShow(moveTaskDODTO.getBatchExpiredDateShow());
            waveStorageDO.setProductionDateShow(moveTaskDODTO.getBatchProductionDateShow());
            String pickModel = moveTaskMapper.getPickModel(waveStorageDO.getWarehouseAreaCode(),warehouseId);
            if(pickModel != null) {
                waveStorageDO.setPickModel(pickModel);
            }
            waveStorageDO.setStatus("0");
            BigDecimal qty = moveTaskDODTO.getQty();
            waveStorageDO.setQty(qty);
            BigDecimal volume = moveTaskDODTO.getVolume();
            if (volume != null) {
                waveStorageDO.setVolume(qty.multiply(volume));
            } else {
                logger.error("补货商品 volume 是空");
            }
            //建立关联关系
            waveStorageDO.setBillId(billId);
            waveStorageDO.setBillDId(billDId);
            waveStorageDO.setMoveTaskId(moveTaskId);
            //@TODO
            waveStorageDO.setWeight(BigDecimal.ZERO);
            waveStorageDO.setItemId(moveTaskDODTO.getItemId());
            if(userDTO!=null){
                ModelUtils.setBaseInfo(billDO, ModelUtils.OpType.INSERT, userDTO);
            }
            waveStorageDOList.add(waveStorageDO);

            //@TODO zz go on
            PickTaskD d = new PickTaskD();
            String pickTaskId = String.valueOf(SnowFlakeUtils.getId(dataCenterId,machineId));
            d.setPickTaskId(pickTaskId);
            d.setWaveStorageId(waveStorageDO.getId());
            d.setWarehouseId(warehouseId);
            d.setQty(waveStorageDO.getQty());
            d.setEnterpriseId(enterpriseId);
            d.setPrintCount(0);
            d.setPickTaskType(0);

            //增加冗余字段
            d.setSrcId(billDO.getSrcId());
            d.setSrcLineId(billDDO.getSrcLineId());
            d.setSrcCode(billDO.getSrcCode());
            d.setBillId(waveStorageDO.getBillId());
            d.setBillDId(waveStorageDO.getBillDId());
            d.setItemId(waveStorageDO.getItemId());
            d.setBatchId(waveStorageDO.getBatchId());
            d.setLocatorId(waveStorageDO.getLocatorId());
            d.setWarehouseAreaCode(waveStorageDO.getWarehouseAreaCode());
            d.setPickModel(waveStorageDO.getPickModel());

            if(userDTO!=null){
                ModelUtils.setBaseInfo(billDO, ModelUtils.OpType.INSERT, userDTO);
            }
            pickTaskDList.add(d);

            PickTaskH h = new PickTaskH();
            h.setPickTaskId(pickTaskId);
            h.setPickTaskType(0);//任务类型（0-整件/1-散件/2-整散合一/3-整单）
            h.setEnterpriseId(enterpriseId);
            h.setWarehouseId(warehouseId);
            h.setStatus("0");
            h.setWarehouseAreaCode(waveStorageDO.getWarehouseAreaCode());
            h.setBldgId(waveStorageDO.getBldgId());
            h.setBldgName(waveStorageDO.getBldgName());
            h.setType(waveStorageDO.getType());
            h.setBillGroupId(billId);
            //修复Bug,tianhl 2021.06.19
            h.setBillId(billId);
            if(userDTO!=null){
                ModelUtils.setBaseInfo(billDO, ModelUtils.OpType.INSERT, userDTO);
            }
            pickTaskHList.add(h);
        }
        if(wmsMoveTaskRefList!=null && wmsMoveTaskRefList.size()>0){
            for (int i = 0; i < wmsMoveTaskRefList.size(); i++) {
                WmsMoveTaskRef refBill=wmsMoveTaskRefList.get(i);
                Criteria moveCri = Criteria.forClass(MoveTaskDO.class);
                moveCri.restrictionBuilder().eq("headerId", refBill.getRefBillId()).eq("detailId", refBill.getRefLineBillId());
                moveCri.buildRestriction();
                List<MoveTaskDO> moveTaskDOs = moveTaskMapper.findByCriteria(moveCri);
                if(moveTaskDOs!=null && moveTaskDOs.size()>0) {
                    refBill.setMoveBillId(moveTaskDOs.get(0).getBillId());
                }
            }
            // wmsMoveTaskRefMapper.saveBatch(wmsMoveTaskRefList);
        }
        rsMap.put("waveBillDOList",waveBillDOList);
        rsMap.put("waveBillDDOList",waveBillDDOList);
    }

    private void getTotalCaseQtyByBuilding(StockWaveResultDetailVO bill, Map<String, BigDecimal> numberByBuilding) {
        List<StockWaveItemDetail> itemDetails = bill.getItemDetails();
        String billId = bill.getBillId();
        logger.info("开始计算订单总体积");
        for (StockWaveItemDetail item : itemDetails) {
            String itemId = item.getItemId();
            WaveBillDDO billD = getWaveBillDDO(billId, itemId);
            mapByBuildingCode(numberByBuilding, item, billD);
        }
    }

    private WaveBillDDO getWaveBillDDO(String billId, String itemId) {
        List<WaveBillDDO> billds = billDMapper.findByMap(MapUtils.put("billId", billId).put("itemId", itemId).getMap(), WaveBillDDO.class);
        AssertUtils.isNotEmpty(billds,"波次单号不能为空");
        return billds.get(0);
    }

    private void mapByBuildingCode(Map<String, BigDecimal> numberByBuilding, StockWaveItemDetail item, WaveBillDDO billd) {
        List<StockWaveLocatorVO> pickLocators = item.getPickLocators();
        Map<String, List<StockWaveLocatorVO>> groupedByBuildingCode =
                pickLocators.stream().collect(Collectors.groupingBy(
                        locator -> Optional.ofNullable(locator.getBuildingCode()).orElse("defaultBuilding")
                ));
        BigDecimal caseConversionRate = billd.getCaseConversionRate();
        for (Map.Entry<String, List<StockWaveLocatorVO>> entry : groupedByBuildingCode.entrySet()) {
            String buildingCode = entry.getKey();
            List<StockWaveLocatorVO> locations = entry.getValue();
            BigDecimal totalQty = locations.stream().
                    map(StockWaveLocatorVO::getQuantity).
                    reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            BigDecimal caseQty = totalQty.divide(caseConversionRate, 2,BigDecimal.ROUND_HALF_UP);
            numberByBuilding.computeIfPresent(buildingCode, (k, v) -> v.add(caseQty));
            numberByBuilding.putIfAbsent(buildingCode, caseQty);
        }
    }

    /**
     * 根据策略找订单集货区
     * @param waveId
     * @param billGroupId
     * @param numberByBuilding
     */
    public List<WorkStageRuntimeDO> calculusWorkspaces(String waveId, String billGroupId, Map<String, BigDecimal> numberByBuilding,
                                                       Integer smallLine,Integer bigLine) {
        List<WaveBillDO> waveBillDOS = billMapper.findByMap(MapUtils.put("billGroupId", billGroupId).getMap(), WaveBillDO.class);
        WaveBillDO billDO = waveBillDOS.get(0);
        String consignorId = billDO.getConsignorId();
        //条件配置
        WorkStageContext workStageContext = buildWorkStageContext(billDO);
        Date date = new Date();
        List<WorkStageRuntimeDO> infos = new ArrayList<>();
        for (Map.Entry<String, BigDecimal> entry : numberByBuilding.entrySet()) {
            String buildingCode = entry.getKey();
            workStageContext.addDefaultSearchCondition("buildingCode", buildingCode);
            BigDecimal totalCase = entry.getValue().setScale(0,BigDecimal.ROUND_UP);//关键：按订单，折合向上取整
            if(totalCase.intValue()<smallLine){
                workStageContext.addDefaultSearchCondition("stageType", "小");
            }else if(totalCase.intValue()>bigLine){
                workStageContext.addDefaultSearchCondition("stageType", "大");
            }else{
                workStageContext.addDefaultSearchCondition("stageType", "中");
            }
            //执行策略
            workStageContext.search(null);
            List<WorkStage> searchResults = workStageContext.getSearchResult();
            List<WorkStage> results = WorkStageAlgorithm.compute(searchResults, totalCase, waveBillDOS);
            for (WorkStage workStage : results) {
                WorkStageRuntimeDO info = new WorkStageRuntimeDO();
                BigDecimal result = workStage.getResult();
                info.setCaseQty(result);
                info.setWaveId(waveId);
                info.setStatus(1);
                info.setConsignorId(consignorId);
                info.setBillGroupId(billGroupId);
                info.setBuildingCode(buildingCode);
                info.setStageId(workStage.getStageId());
                info.setStageCode(workStage.getCode());
                info.setStageType(workStage.getInfo().getStageType());
                info.setEnterpriseId(workStage.getInfo().getEnterpriseId());
                info.setGmtCreate(date);
                info.setGmtModified(date);
                info.setShipperId(billDO.getShipperId());
                infos.add(info);
            }
        }
        logger.error("订单：{} 集货区结果为：{}", billGroupId, JSON.toJSONString(infos));
        return infos;
    }

    //集货区当前情况
    private WorkStageContext buildWorkStageContext(WaveBillDO billDO) {
        WorkStageContext workStageContext = new WorkStageContext();
        String warehouseId = billDO.getWarehouseId();
        String srcId = billDO.getSrcId();
        BeanMap beanMap = BeanMap.create(billDO);
        workStageContext.addMatchVariables(beanMap);

        String shipLineId = billDO.getShipLineId();
        String billType = billDO.getType();
        //字段来自集货区数据库字段
        workStageContext.addDefaultSearchCondition("warehouseId", warehouseId);
        //收货方式
        if(StringUtils.isBlank(billDO.getReceiveWay())){
            throw new BizException("提货方式为空无法分配集货区!");
        }
        workStageContext.addDefaultSearchCondition("deliveryType", billDO.getReceiveWay());
        //配送方向
        if(StringUtils.isBlank(shipLineId)){
            throw new BizException("配送方向为空无法分配集货区!");
        }
        workStageContext.addDefaultSearchCondition("shipLineId", shipLineId);
        return workStageContext;
    }

    private void assignLocators(WaveBillDDO billDDO, InvStockWaveItem invStockWaveItem) {
        String billDId = billDDO.getSrcLineId();
        Map<String, Object> queryMap = MapUtils.put("billDId", billDId).getMap();
        List<BillDPrePositionDO> byMap = billDPrePositionMapper.findByMap(queryMap, BillDPrePositionDO.class);
        if (byMap != null) {
            List<InvStockWaveItemStock> collect = byMap.stream().map(this::getInvStockWaveItemStock).collect(Collectors.toList());
            invStockWaveItem.setItemStocks(collect);
        }
    }

    private InvStockWaveItemStock getInvStockWaveItemStock(BillDPrePositionDO location) {
        InvStockWaveItemStock invLocator = new InvStockWaveItemStock();
        BigDecimal qty = location.getQty();
        String locationId = location.getLocationId();
        invLocator.setLocatorId(locationId);
        invLocator.setQuantity(qty);
        invLocator.setBatchId(location.getBatchId());
        return invLocator;
    }

    private void updateBillInfo(WaveBillDO bill) {
        String billId = bill.getId();
        Map<String, Object> totalInfo = billMapper.getTotalInfo(null, billId);
        BigDecimal totalQty = (BigDecimal) totalInfo.get("PIECE_QTY");
        BigDecimal totalVolume = (BigDecimal) totalInfo.get("TOTAL_VOLUME");
        BigDecimal totalWeight = (BigDecimal) totalInfo.get("TOTAL_WEIGHT");
        BigDecimal totalPcs = (BigDecimal) totalInfo.get("TOTAL_PCS");
        bill.setTotalVolume(totalVolume);
        bill.setTotalQty(totalQty);
        bill.setTotalWeight(totalWeight);
        bill.setTotalPcs(totalPcs);
        bill.setStatus(BILL_STATUS.DISTRIBUTED);
    }

    /**
     * 检查商品是否为近效期商品，是则提示
     *
     * @param wBillDList
     * @param enterpriseId
     * @param warehouseId
     * @param cargoOwnerId
     * @return
     */
    private String checkNearExpiryDate(List<WaveBillDDO> wBillDList, String enterpriseId, String warehouseId,
                                       String cargoOwnerId) {
        String resMsg = null;
        List<String> msgList = new ArrayList<>();
        String[] itemIds = wBillDList.stream().map(WaveBillDDO::getItemId).toArray(String[]::new);
        // 根据id查询商品批次信息，用来获取有效期
        RestMessage<List<WmsInvBatchNumbers>> restMessage = invClient.getBatchInfosByItems(itemIds);
        RestMessage<List<BaseMtlItemMst>> itemResult = itemClient.getListByIds(new HashSet<>(Arrays.asList(itemIds)));
        if (restMessage.isSuccess() && ObjectUtils.isNotNull(restMessage.getData())) {
            List<WmsInvBatchNumbers> batchNumbers = restMessage.getData();
            // 用 商品id+批次号 做 key
            Map<String, Date> dateMap = batchNumbers.stream().collect(Collectors.toMap(
              (entity) -> entity.getItemId() + entity.getBatchNum(), WmsInvBatchNumbers::getBatchExpiredDate));

            // 获取货主的 近效期天数 业务配置
            Object value = strategySelector.selectHandle(Arrays.asList(STRATEGY_CODE.CARGO_OWNER__NEAR_EXPIRY_DATE_DAY),
              enterpriseId, warehouseId, null, cargoOwnerId).getResult();
            Integer ned = Integer.valueOf(String.valueOf(ObjectUtils.isNull(value) ?
              STRATEGY_CODE.CARGO_OWNER__NEAR_EXPIRY_DATE_DAY.getDefaultValue() : value));

            for (WaveBillDDO ddo : wBillDList) {
                String key = ddo.getItemId() + ddo.getBatchNum();
                BigDecimal shelfLifeDays=BigDecimal.ZERO;
                if(itemResult!=null && itemResult.getData()!=null && itemResult.getData().size()>0){
                    BaseMtlItemMst BaseMtlItemMst=itemResult.getData().stream().
                            filter(t->t.getItemId().equals(ddo.getItemId())).findFirst().orElse(null);
                    if(BaseMtlItemMst!=null && BaseMtlItemMst.getBaseMtlItemWms()!=null){
                        shelfLifeDays = BaseMtlItemMst.getBaseMtlItemWms().getShelfLifeDays();
                    }
                }
                if (dateMap.containsKey(key)) {
                    Date expiredDate = dateMap.get(key);
                    int days = (int) ((expiredDate.getTime() - (new Date()).getTime()) / (1000*3600*24));
                    // 比较 当前时间 + 近效天数 和 过期时间，在 过期时间 之后的，则需要 近效期提示
                    if (days<ned) {
                        msgList.add(ddo.getItemName() + "(" + ddo.getItemId() + "/" + ddo.getManufacturerLotNum() + ")小于近效期");
                    }
                    if (days<shelfLifeDays.intValue()) {
                        msgList.add(ddo.getItemName() + "(" + ddo.getItemId() + "/" + ddo.getManufacturerLotNum() + ")小于质保期");
                    }
                }
            }
            if (msgList.size() > 0) {
                resMsg = String.join("，", msgList);
            }
        }

        return resMsg;
    }

    /**
     * 生成合单号
     * @param mergedIdMap
     * @param contactTel
     * @return
     */
    private String generateMergedId(Map<String, String> mergedIdMap, String contactTel) {
        String mergedId;
        if (StrUtil.isNotBlank(contactTel) && mergedIdMap.containsKey(contactTel)) {
            mergedId = mergedIdMap.get(contactTel);
        } else {
            mergedId = StrUtil.uuid().replaceAll("-", "");
            if (StrUtil.isNotBlank(contactTel)) {
                mergedIdMap.put(contactTel, mergedId);
            }
        }
        return mergedId;
    }

    /**
     * 生成发运号
     * @param shipBillNoMap
     * @param billNo    订单号/合单号
     * @return
     */
    private String generateShipBillNo(Map<String, String> shipBillNoMap, String billNo) {
        String shipBillNo;
        if (shipBillNoMap.containsKey(billNo)) {
            shipBillNo = shipBillNoMap.get(billNo);
        } else {
            shipBillNo = getRandomNumber(9);
            shipBillNoMap.put(billNo, shipBillNo);
        }
        return shipBillNo;
    }

    public static String getRandomNumber(int length) {
        StringBuilder val = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < length; ++i) {
            val.append(random.nextInt(10));
        }

        return val.toString();
    }


}
