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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import com.prolog.cs.basic.common.model.print.PrintParam;
import com.prolog.cs.basic.warehouse.model.BaseWmsWarehouseArea;
import com.prolog.cs.basic.warehouse.model.vo.WarehouseLocatorBasicInfo;
import com.prolog.cs.biz.outbound.core.SpringUtils;
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.PickTaskDDTO;
import com.prolog.cs.biz.outbound.model.dto.WorkStageTransferDTO;
import com.prolog.cs.biz.outbound.model.entity.*;
import com.prolog.cs.biz.outbound.model.vo.CargoOwnerChangeVO;
import com.prolog.cs.biz.outbound.model.vo.TaskParamsVO;
import com.prolog.cs.biz.outbound.model.vo.WavePreCalResultVO;
import com.prolog.cs.biz.outbound.model.vo.WaveStorageVO;
import com.prolog.cs.biz.outbound.resource.InventoryResource;
import com.prolog.cs.biz.outbound.resource.OperateResource;
import com.prolog.cs.biz.outbound.resource.StrategyResource;
import com.prolog.cs.biz.outbound.resource.WarehouseResource;
import com.prolog.cs.biz.outbound.service.MoveTaskService;
import com.prolog.cs.biz.outbound.service.PackService;
import com.prolog.cs.biz.outbound.service.WorkStageService;
import com.prolog.cs.biz.outbound.service.impl.eis.EisOutBoundHandler;
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.BillUtils;
import com.prolog.cs.biz.outbound.utils.StatusUtil;
import com.prolog.entity.vo.OutBoundRequestVO;
import com.prolog.framework.authority.core.dto.UserDTO;
import com.prolog.framework.authority.user.UserTemplate;
import com.prolog.framework.authority.util.HystrixVarUtil;
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.ParameterException;
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.vo.*;
import com.prolog.upcloud.base.strategy.domain.core.Strategy;
import com.prolog.upcloud.base.strategy.dto.StrategyDTO;
import com.prolog.upcloud.base.strategy.model.BusinessControl;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.HashedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.prolog.cs.biz.outbound.model.constant.CacheKeyConstant.OUT_BOUND_REQUEST_WAVE_ID;

@Slf4j
@Service
public class WorkStageServiceImpl implements WorkStageService {

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

    @Autowired
    private WaveStorageMapper waveStorageMapper;

    @Autowired
    private WorkStageRuntimeMapper workStageMapper;

    @Autowired
    WaveBillMapper billMapper;

    @Autowired
    WaveBillDMapper billDMapper;

    @Autowired
    private WaveMapper waveMapper;

    @Autowired
    private MoveTaskMapper moveTaskMapper;

    @Autowired
    private MoveTaskService moveTaskService;

    @Autowired
    private UserTemplate userTemplate;

    @Autowired
    private WmsMoveTaskRefMapper wmsMoveTaskRefMapper;

    @Autowired
    private InventoryResource inventoryResource;

    @Autowired
    private WarehouseResource warehouseResource;

    @Autowired
    private StrategyResource strategyResource;

    @Autowired
    private PackService packService;

    @Autowired
    private OmsDoBillDMapper omsDoBillDMapper;

    @Autowired
    private PickTaskHMapper pickTaskHMapper;

    @Autowired
    PickTaskDMapper pickTaskDMapper;

    @Autowired
    private InventoryResource invClient;

    @Autowired
    private OperateResource operateResource;

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

    @Value("${prolog.dao.keyGenerator.snowFlake.machineId:13}")
    private long machineId;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private EisOutBoundHandler eisOutBoundHandler;

    WaveDistributeFunc.stockWaveFail stockWaveFail;
    WaveDistributeFunc.updateWaveStatus updateWaveStatus;
    WaveDistributeFunc.updateWaveBillStatus updateWaveBillStatus;
    WaveDistributeFunc.updateCollectionArea updateCollectionArea;

    @PostConstruct
    public void init() {
        stockWaveFail = this::stockWaveFail;
        updateWaveStatus = this::updateWaveStatus;
        updateWaveBillStatus = this::updateWaveBillStatus;
        updateCollectionArea = this::updateCollectionArea;
    }

    /**
     * 接收波次处理结构消息，并进行打印
     * @param result 库存返回的处理信息
     */
    @RabbitListener(queuesToDeclare = @Queue(value = "waveResponseQueue"))
    public void handleWaveHandleResult(String result, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
        String waveId = null;
        try {
            long startTime = System.currentTimeMillis();
            StockWaveResultVO waveResult = JsonUtils.parseJsonString(result, new TypeReference<StockWaveResultVO>() {
                @Override
                public Type getType() {
                    return super.getType();
                }
            });
            waveId = waveResult == null ? null:waveResult.getWaveId();
            logger.info("波次下发={}:{}",waveId,result);
            handle(waveResult,false);
            long endTime = System.currentTimeMillis();
            logger.info("生成拣选任务耗时:{}",endTime-startTime);
            //随货赠品按订单完成状态直接系统自动完成拣货及核减库存
            handleGift(waveId);
        } catch (Throwable e) {
            logger.error("处理库存服务返回的波次信息失败,波次ID={}:",waveId,e);
            logger.warn("库存服务返回的波次信息:{}",result);
        }finally {
            channel.basicAck(tag,false);
        }
    }

    @Override
    @Transactional
    public void tryHandle(StockWaveResultVO waveResult, List<WavePreCalResultVO> resultList) {
        //更新订单状态
        waveMapper.updateMapById(waveResult.getWaveId(),MapUtils.put("status", BILL_STATUS.DISTRIBUTED).getMap(),WaveDO.class);
        handle(waveResult,true);
        Set billIdSet = waveResult.getDetails().stream().map(StockWaveResultDetailVO::getBillId).distinct().collect(Collectors.toSet());
        Criteria crt = Criteria.forClass(WmsMoveTaskRef.class);
        Restriction r1 = Restrictions.in("billId", billIdSet.toArray());
        crt.setRestriction(Restrictions.and(r1));
        List<WmsMoveTaskRef> wmsMoveTaskRefList = wmsMoveTaskRefMapper.findByCriteria(crt);
        Set moveBillIdSet = wmsMoveTaskRefList.stream().map(WmsMoveTaskRef::getMoveBillId).distinct().collect(Collectors.toSet());
        moveBillIdSet.addAll(billIdSet);

        Criteria crt2 = Criteria.forClass(WaveBillDDO.class);
        Restriction r12 = Restrictions.in("billId", billIdSet.toArray());
        crt2.setRestriction(Restrictions.and(r12));
        List<WaveBillDDO> waveBillDDOList = billDMapper.findByCriteria(crt2);

        Criteria cr = Criteria.forClass(PickTaskH.class);
        cr.restrictionBuilder().in("billId", moveBillIdSet.toArray());
        cr.buildRestriction();
        List<PickTaskH> pickTaskHs = pickTaskHMapper.findByCriteria(cr);
        Map<String, List<PickTaskH>> groupBy = pickTaskHs.stream().collect(Collectors.groupingBy(PickTaskH::getWarehouseAreaCode));
        WaveDO waveDo = waveMapper.findById(waveResult.getWaveId(), WaveDO.class);
        RestMessage<List<BaseWmsWarehouseArea>> areas = warehouseResource.getByAreaCodes(waveResult.getWarehouseId(), pickTaskHs.stream().map(t->t.getWarehouseAreaCode()).toArray(String[]::new));
        for (String warehouseAreaCode : groupBy.keySet()) {
            List<PickTaskH> pickTaskHS = groupBy.get(warehouseAreaCode);
            WavePreCalResultVO wavePreCalResultVO=new WavePreCalResultVO();
            wavePreCalResultVO.setWaveCode(waveDo==null?null:waveDo.getCode());
            if (areas!=null && areas.isSuccess() && areas.getData()!=null && areas.getData().size()>0) {
                BaseWmsWarehouseArea area=areas.getData().stream().filter(t->t.getWarehouseAreaCode().equals(warehouseAreaCode)).findFirst().orElse(null);
                if (area!=null) {
                    wavePreCalResultVO.setWarehouseAreaName(area.getWarehouseAreaName());
                }
            }
            wavePreCalResultVO.setWarehouseAreaCode(warehouseAreaCode);
            wavePreCalResultVO.setNumOfTask(pickTaskHS.size());
            wavePreCalResultVO.setNumOfBills(billIdSet.size());
            Criteria cr1 = Criteria.forClass(PickTaskD.class);
            cr1.restrictionBuilder().in("pickTaskId", pickTaskHS.stream().map(t->t.getPickTaskId()).toArray(String[]::new));
            cr1.buildRestriction();
            List<PickTaskD> pickTaskDs = pickTaskDMapper.findByCriteria(cr1);
            wavePreCalResultVO.setNumOfTaskLine(pickTaskDs.size());
            Criteria cr2 = Criteria.forClass(WaveStorageDO.class);
            cr2.restrictionBuilder().in("id", pickTaskDs.stream().map(t->t.getWaveStorageId()).toArray(String[]::new));
            cr2.buildRestriction();
            List<WaveStorageDO> waveStorageVOs = waveStorageMapper.findByCriteria(cr2);
            Set set = new HashSet();
            BigDecimal caseQty=BigDecimal.ZERO;
            for (int i = 0; i < waveStorageVOs.size(); i++) {
                WaveStorageDO vo=waveStorageVOs.get(i);
                set.add(vo.getItemId()+vo.getManufactureLotNum());
                WaveBillDDO WaveBillDDO=waveBillDDOList.stream().filter(t->t.getId().equals(vo.getBillDId())).findFirst().orElse(null);
                if (WaveBillDDO!=null && WaveBillDDO.getCaseConversionRate().compareTo(BigDecimal.ZERO)>0) {
                    caseQty =caseQty.add(vo.getQty().divide(WaveBillDDO.getCaseConversionRate(), 2,BigDecimal.ROUND_HALF_UP));
                }
            }
            wavePreCalResultVO.setNumOfItems(set.size());
            wavePreCalResultVO.setNumOfCases(caseQty.setScale( 0, BigDecimal.ROUND_UP).intValue());
            resultList.add(wavePreCalResultVO);
        }
        throw new BizException("试算不修改数据库回滚");
    }

    /**
     * 接收波次处理结构消息，并进行打印
     * @param waveResult waveResult
     */
    @Flow(name = "接收波次处理结构消息", category = "出库/波次管理/接收波次处理结构消息", index = 3, description = "接收波次处理结构消息")
    public void handle(StockWaveResultVO waveResult, boolean isTry) {
        WaveDO waveDO = validWaveHandle(waveResult);
        if (waveDO == null) {
            return;
        }
        String waveId = waveResult.getWaveId();
        List<StockWaveResultDetailVO> details = waveResult.getDetails();
        // 处理失败订单
        updateWaveBillStatus.updateWaveBillStatus(details.iterator());
        List<StockWaveResultDetailVO> successBillsResults = extractedBatchLimit(waveResult);
        if (CollUtil.isEmpty(successBillsResults)) {
            logger.error("successBillsResults == null waveId={}", waveId);
            return;
        }
        if (isTry) {
            moveTaskService.extractedMoveNoTx(waveResult, successBillsResults);
        } else {
            moveTaskService.extractedMove(waveResult, successBillsResults);
        }
        // 获取集货区订单类型配置
        Integer smallLine = 0, bigLine = 0;
        RestMessage<Map<String, BusinessControl>> rest = strategyResource.getControlByCodes(
                new String[]{"outbound_work_stage_small_line", "outbound_work_stage_big_line"},
                waveDO.getWarehouseId(), null, waveDO.getEnterpriseId(), null);
        if (rest.isSuccess() && rest.getData() != null) {
            Map<String, BusinessControl> conMap = rest.getData();
            if (conMap.containsKey("outbound_work_stage_small_line")) {
                smallLine = Integer.valueOf(conMap.get("outbound_work_stage_small_line").getRegValue());
            }
            if (conMap.containsKey("outbound_work_stage_big_line")) {
                bigLine = Integer.valueOf(conMap.get("outbound_work_stage_big_line").getRegValue());
            }
        }
        boolean useStage = false;
        List<String> workStageList = new ArrayList<>();
        RestMessage<BusinessControl> restWorkStage = strategyResource.getControlByCode("YJ_WORK_STAGE_CONTROL", waveDO.getWarehouseId(), null, waveDO.getEnterpriseId(), null);
        if (restWorkStage.isSuccess() && restWorkStage.getData() != null) {
            if (restWorkStage.getData().getRegValue().contains(",")) {
                workStageList = Arrays.asList(restWorkStage.getData().getRegValue().split(","));
            } else {
                workStageList = Collections.singletonList(restWorkStage.getData().getRegValue());
            }
        }
        if (CollectionUtil.isNotEmpty(workStageList) && workStageList.contains(waveDO.getType())) {
            useStage = true;
        }
        logger.info("开始计算集货区 waveId={}", waveId);
        Map<String, List<StockWaveResultDetailVO>> groupByBillGroup = successBillsResults.stream().collect(Collectors.groupingBy(StockWaveResultDetailVO::getBillGroupId));
        List<WorkStageRuntimeDO> workStageRuntimeDOs = new ArrayList<>();
        List<WaveBillDO> stageErrorWaveBillDOs = new ArrayList<>();
        StatusUtil.updateWaveStartStatus(waveId, WAVE_STATUS.WORK_STAGE, null);
        for (String billGroupId : groupByBillGroup.keySet()) {
            String exceptionMsg = "";
            if (useStage) {
                // 计算集货区
                try {
                    logger.info("计算订单对应的集货区,billGroupId={} ", billGroupId);
                    Map<String, BigDecimal> numberByBuilding = new HashMap<>();
                    // 按照建筑物计算总箱数
                    groupByBillGroup.get(billGroupId).forEach(bill -> {
                        getTotalCaseQtyByBuilding(bill, numberByBuilding);
                    });
                    workStageRuntimeDOs.addAll(calculusWorkspaces(waveId, billGroupId, numberByBuilding, smallLine, bigLine));
                } catch (BizException e) {
                    logger.error("计算集货区出错", e);
                    exceptionMsg = e.getMessage();
                } catch (Exception e) {
                    logger.error("计算集货区出错", e);
                    exceptionMsg = "计算集货区异常!";
                }
            }
            Criteria cr = Criteria.forClass(WaveBillDO.class);
            cr.restrictionBuilder().eq("billGroupId", billGroupId);
            cr.buildRestriction();
            List<WaveBillDO> waveBillDOs = billMapper.findByCriteria(cr);
            if (StringUtils.isBlank(exceptionMsg)) {
                for (int i = 0; i < waveBillDOs.size(); i++) {
                    // 更新订单状态
                    WaveBillDO waveBillDO = waveBillDOs.get(i);
                    waveBillDO.setStatus(BILL_STATUS.WORKSTAGE_FOUND);
                    billMapper.updateFields(waveBillDO, FieldSelector.newInstance().include("status"));
                }
                waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.WORK_STAGE).getMap(), WaveDO.class);
                for (StockWaveResultDetailVO bill : groupByBillGroup.get(billGroupId)) {
                    String billId = bill.getBillId();
                    WaveBillDO billDo = billMapper.findById(billId, WaveBillDO.class);
                    if (billDo == null) {
                        logger.error("找不到订单信息,billGroupId={},billId={}", billGroupId, billId);
                        continue;
                    }
                    // 保存商品预占明细
                    logger.info("更新订单对应的预占信息,billId={},billGroupId={}", billId, billGroupId);
                    updateLocators(waveResult, bill, billId);
                }
            } else {
                stageErrorWaveBillDOs.addAll(waveBillDOs);
                for (int i = 0; i < waveBillDOs.size(); i++) {
                    WaveBillDO waveBillDO = waveBillDOs.get(i);
                    BillUtils.updateStatus(waveBillDO.getId(), BILL_STATUS.WORKSTAGE_ERROR, exceptionMsg);
                }
            }
        }
        StatusUtil.updateWaveEndStatus(waveId, WAVE_STATUS.WORK_STAGE, null);
        // 出库预占失败,删除库存的预占
        if (stageErrorWaveBillDOs.size() > 0) {
            rollBackApi(stageErrorWaveBillDOs);
        }
        if (workStageRuntimeDOs.size() > 0) {
            workStageMapper.saveBatch(workStageRuntimeDOs);
        } else if (useStage) {
            waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.WORK_STAGE).put("error_msg", "计算集货区失败").getMap(), WaveDO.class);
            return;
        }
        logger.info("开始计算拣货容器任务 wave id={}", waveId);
        String exceptionMsg = "";
        try {
            if (isTry) {
                createPickTaskNoTx(waveResult, waveDO.getType());
            } else {
                createPickTask(waveResult, waveDO.getType());
            }
            waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.COMPLETED).getMap(), WaveDO.class);
            StatusUtil.updateWaveStartStatus(waveId, WAVE_STATUS.COMPLETED, null);
            StatusUtil.updateWaveEndStatus(waveId, WAVE_STATUS.COUNT_CONTAINER, null);
            sendEisIfExists(waveId);
        } catch (Throwable e) {
            exceptionMsg = "计算波次对应容器任务失败";
            waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.COUNT_CONTAINER).getMap(), WaveDO.class);
            logger.error("计算波次对应容器任务失败 wave id={}", waveId, e);
        }
        if (!StringUtils.isBlank(exceptionMsg)) {
            waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.WORK_STAGE).put("error_msg", exceptionMsg).getMap(), WaveDO.class);
        }

    }

    /**
     * 将缓存中的消息推送给EIS
     *
     * @param waveId
     */
    public void sendEisIfExists(String waveId) {
        //推送EIS出库任务
        String outBoundRequestWaveId = OUT_BOUND_REQUEST_WAVE_ID + waveId;
        if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(outBoundRequestWaveId))) {
            return;
        }
        String json = stringRedisTemplate.opsForValue().get(outBoundRequestWaveId);
        List<OutBoundRequestVO> list = JSONObject.parseArray(json, OutBoundRequestVO.class);
        RestMessage<List<OutBoundRequestVO>> repSendEisParamsByWaveId = operateResource.getRepSendEisParamsByWaveId(waveId);
        if(repSendEisParamsByWaveId == null || !repSendEisParamsByWaveId.isSuccess()){
            throw new BizException("调用operator获取补货推送eis入参失败!");
        }
        if(repSendEisParamsByWaveId.getData() != null && repSendEisParamsByWaveId.getData().size() > 0){
            list.addAll(repSendEisParamsByWaveId.getData());
        }
        eisOutBoundHandler.outBoundRequest(list);
        stringRedisTemplate.delete(outBoundRequestWaveId);
    }

    private List<StockWaveResultDetailVO> extractedBatchLimit(StockWaveResultVO waveResult) {
        List<StockWaveResultDetailVO> successBillsResults = waveResult.getDetails().stream().filter(StockWaveResultDetailVO::isSuccess).collect(Collectors.toList());
        if (CollUtil.isEmpty(successBillsResults)) {
            return successBillsResults;
        }
        List<StockWaveItemDetail> lineList = new ArrayList<>(successBillsResults.size());
        Map<String,String> lineIdMap =new HashMap();
        for (int i = 0; i < successBillsResults.size(); i++) {
            StockWaveResultDetailVO stockWaveResultDetailVO = successBillsResults.get(i);
            lineList.addAll(stockWaveResultDetailVO.getItemDetails());

            for (int j = 0; j < stockWaveResultDetailVO.getItemDetails().size(); j++) {
                lineIdMap.put(stockWaveResultDetailVO.getItemDetails().get(j).getSrcLineId(),stockWaveResultDetailVO.getBillId());
            }
        }
        Map<String, List<StockWaveItemDetail>> groupBySrcLineId = lineList.stream().collect(Collectors.groupingBy(StockWaveItemDetail::getSrcLineId));
        Criteria cr1 = Criteria.forClass(OmsDoBillDDO.class);
        Restriction r1 = Restrictions.in("lineid", groupBySrcLineId.keySet().toArray());
        Restriction r2 = Restrictions.isNotNull("batchLimit");
        cr1.setRestriction(Restrictions.and(r1,r2));
        List<OmsDoBillDDO> billDList = omsDoBillDMapper.findByCriteria(cr1);
        if (CollUtil.isEmpty(billDList)) {
            return successBillsResults;
        }
        String waveId = waveResult.getWaveId();
        for (int i = 0; i < billDList.size(); i++) {
            OmsDoBillDDO omsDoBillDDO = billDList.get(i);
            int batchLimit = omsDoBillDDO.getBatchLimit();
            List<StockWaveItemDetail> list = groupBySrcLineId.get(omsDoBillDDO.getLineid());
            Set<String> set = new HashSet<>();
            for (int j = 0; j < list.size(); j++) {
                List<StockWaveLocatorVO> subList = list.get(j).getPickLocators();
                for (int k = 0; k < subList.size(); k++) {
                    set.add(subList.get(k).getManufacturerLotNum());
                }
            }
            if (batchLimit < set.size()) {
                String billId = lineIdMap.get(omsDoBillDDO.getLineid());
                String errorMsg = "商品[" + omsDoBillDDO.getItemname() + "]批号个数[" + set.size() + "]超过批号上限个数[" + batchLimit + "]";
                BillUtils.updateStatus(billId, BILL_STATUS.BATCH_LIMIT, errorMsg);
                StatusUtil.updateWaveEndStatus(waveId, WAVE_STATUS.BATCH_LIMIT, null);
                successBillsResults = successBillsResults.stream().filter(t -> !billId.equals(t.getBillId())).collect(Collectors.toList());
            }
        }
        return successBillsResults;
    }

    private void rollBackApi(List<WaveBillDO> waveBillDOs) {
        for (int i = 0; i < waveBillDOs.size(); i++) {
            BillUtils.updateStatus(waveBillDOs.get(i).getId(), BILL_STATUS.WORKSTAGE_ERROR);
        }
        String[] billIds = waveBillDOs.stream().map(WaveBillDO::getId).toArray(String[]::new);
        Criteria cri = Criteria.forClass(WaveBillDDO.class);
        cri.restrictionBuilder().in("billId", billIds);
        cri.buildRestriction();
        List<WaveBillDDO> waveBillDDOs = billDMapper.findByCriteria(cri);
        String[] lineBillIds = waveBillDDOs.stream().map(WaveBillDDO::getId).toArray(String[]::new);
        if (lineBillIds!=null && lineBillIds.length>0) {
            InvStockCancelOut stockCancel = new InvStockCancelOut();
            stockCancel.setLineBillIds(lineBillIds);
            stockCancel.setOutType("1");
            logger.info("inventoryResource.outCancel，stockCancel={}", JSON.toJSONString(stockCancel));
            RestMessage restMessage = inventoryResource.outCancel(stockCancel);
            if (!restMessage.isSuccess()) {
                logger.error("inventoryResource.outCancel，restMessage={}", JSON.toJSONString(restMessage));
                throw new BizException("扣减库存失败: " + restMessage.getMessage());
            } else {
                logger.info("inventoryResource.outCancel，restMessage={}", JSON.toJSONString(restMessage));
            }
        }
    }

    private WaveDO validWaveHandle(StockWaveResultVO waveResult) {
        if (waveResult == null) {
            return null;
        }
        if (!waveResult.isSuccess()) {
            stockWaveFail.stockWaveFail(waveResult);
            return null;
        }
        String waveId = waveResult.getWaveId();
        WaveDO waveDO = waveMapper.findById(waveId, WaveDO.class);
        if (waveDO == null) {
            logger.error("找不到对应的波次");
            return null;
        }
        if (waveDO.getStatus() != WAVE_STATUS.DISTRIBUTED) {
            logger.error("已经收到过波次消息,不再处理");
            return null;
        }

        List<StockWaveResultDetailVO> billsResults = waveResult.getDetails();
        if (billsResults.isEmpty()) {
            logger.warn("该波次所有订单预占失败");
            waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.ERROR).getMap(), WaveDO.class);
            return null;
        }
        String[] bIllIds = billsResults.stream().map(StockWaveResultDetailVO::getBillId).distinct().toArray(String[]::new);
        Criteria crt = Criteria.forClass(WaveBillDO.class);
        crt.restrictionBuilder().in("status",new String[]{"WORKSTAGE_FOUND", "COMPLETED"}).in("id",bIllIds);
        crt.buildRestriction();
        long count = billMapper.findCountByCriteria(crt);
        if (count != 0L) {
            logger.error("波次订单已处理,不再处理,waveId={}", waveId);
            return null;
        }
        // 更新wave状态为库存已经预占,则时插入状态码信息到状态码表
        updateWaveStatus.updateWaveStatus(waveId);
        return waveDO;
    }

    /**
     * 库存预占失败
     */
    @FuncImpl(name = "库存预占失败", func = WaveDistributeFunc.stockWaveFail.class, description = "库存预占失败,根据waveId更新wms_wave表状态为错误,同时更新错误信息")
    private void stockWaveFail(StockWaveResultVO waveResult) {
        String errorMsg = waveResult.getErrorMsg();
        Map<String, Object> updateStatus = MapUtils.put("status", WAVE_STATUS.ERROR).put("error_msg", errorMsg).getMap();
        waveMapper.updateMapById(waveResult.getWaveId(), updateStatus, WaveDO.class);
        // 更新波次订单表状态为找不到集货区并更新错误信息
        Criteria crt = Criteria.forClass(WaveBillDO.class);
        crt.restrictionBuilder().eq("waveId",waveResult.getWaveId());
        crt.buildRestriction();
        billMapper.updateMapByCriteria(MapUtils.put("status", "WORKSTAGE_ERROR").put("errorMsg", errorMsg).getMap(), crt);
        logger.warn("库存预占失败，waveId={} errorMsg={}", waveResult.getWaveId(), errorMsg);
    }

    /**
     * 更新WAVE表状态
     */
    @FuncImpl(name = "更新WAVE表状态", func = WaveDistributeFunc.updateWaveStatus.class, description = "根据waveId更新wms_wave表状态为库存已经预占,同时插入状态码信息到WMS_STATUS_RECORD表")
    private void updateWaveStatus(String waveId) {
        Map<String, Object> updateStatus = MapUtils.put("status", WAVE_STATUS.POSITIONED).getMap();
        waveMapper.updateMapById(waveId, updateStatus, WaveDO.class);
        StatusUtil.updateWaveStartStatus(waveId, WAVE_STATUS.POSITIONED, null);
        StatusUtil.updateWaveEndStatus(waveId, WAVE_STATUS.DISTRIBUTED, null);
    }

    /**
     * 处理订单,如果订单预占成功,根据billId更新wms_wave_bill表状态为“预占库存完毕”,反之,根据billId查询wms_wave_bill表
     * 判断是否合单,若不为1,根据billId更新wms_wave_bill表状态为“预占库存失败”,同时更新errorMsg,
     * 若为1,根据billId删除wms_wave_bill表信息,同时根据billId删除wms_wave_bill_d表信息;
     */
    @FuncImpl(name = "更新WAVEBILL表状态", func = WaveDistributeFunc.updateWaveBillStatus.class, description = "处理订单,如果订单预占成功,根据billId更新wms_wave_bill表状态为“预占库存完毕”,反之,根据billId查询wms_wave_bill表,判断是否合单,若不为1,根据billId更新wms_wave_bill表状态为“预占库存失败”,同时更新errorMsg,若为1,根据billId删除wms_wave_bill表信息,同时根据billId删除wms_wave_bill_d表信息;")
    private void updateWaveBillStatus(Iterator<StockWaveResultDetailVO> it) {
        while (it.hasNext()) {
            StockWaveResultDetailVO invBill = it.next();
            String billGroupId = invBill.getBillGroupId();
            if (!invBill.isSuccess()) {
                String billId = invBill.getBillId();
                WaveBillDO billDO = billMapper.findById(billId, WaveBillDO.class);
                if(billDO != null) {
                    Integer isMerged = billDO.getIsMerged();
                    if (isMerged.equals(1)) {
                        // 合单后有失败的订单后删除对应的波次订单
                        billMapper.deleteById(billId, WaveBillDO.class);
                        billDMapper.deleteByMap(MapUtils.put("billId", billId).getMap(), WaveBillDDO.class);
                    } else {
                        String errorMsg = invBill.getErrorMsg(); // UTF8到oracle ZHS16GBK，128会超长
                        String subErrorMsg = errorMsg.length() > 1500 ? errorMsg.substring(0, 1500) : errorMsg;
                        BillUtils.updateStatus(invBill.getBillId(), BILL_STATUS.POSITION_ERROR, subErrorMsg);
                    }
                    logger.warn("订单预占失败id billGroup id is ：{}  ", billGroupId);
                    it.remove();
                }
            } else {
                logger.info("订单 {} 预占成功  ", billGroupId);
                BillUtils.updateStatus(invBill.getBillId(), BILL_STATUS.POSITIONED);
            }
        }
    }

    /**
     * 更新集货区状态
     */
    @FuncImpl(name = "更新集货区状态", func = WaveDistributeFunc.updateCollectionArea.class, description = "根据waveId更新wms_wave表状态为计算集货区,同时插入状态码信息到WMS_STATUS_RECORD表")
    private void updateCollectionArea(String waveId) {
        waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.WORK_STAGE).getMap(), WaveDO.class);
        StatusUtil.updateWaveStartStatus(waveId, WAVE_STATUS.WORK_STAGE, null);
        StatusUtil.updateWaveEndStatus(waveId, WAVE_STATUS.POSITIONED, null);
    }

    @Transactional
    public void createPickTask(StockWaveResultVO waveResult,String type) {
        createPickTaskNoTx(waveResult,type);
    }
    private void createPickTaskNoTx(StockWaveResultVO waveResult,String type) {
        String waveId = waveResult.getWaveId();
        List<TaskParamsVO> pickTaskParams = new ArrayList<>();
        TaskParamsVO taskParamsVO = new TaskParamsVO();
        String enterpriseId = waveResult.getEnterpriseId();
        taskParamsVO.setEnterpriseId(enterpriseId);
        String warehouseId = waveResult.getWarehouseId();
        taskParamsVO.setWarehouseId(warehouseId);
        taskParamsVO.setWaveId(waveId);
        pickTaskParams.add(taskParamsVO);
        if ("1".equals(type) || "2".equals(type) || "5".equals(type) || "6".equals(type)) {
            // 正常组包
            packService.createPickTask(pickTaskParams,type);
        } else {
            // 特殊订单组包
            packService.createPickTaskSpecial(pickTaskParams);
        }
        waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.COUNT_CONTAINER).getMap(), WaveDO.class);
        StatusUtil.updateWaveStartStatus(waveId, WAVE_STATUS.COUNT_CONTAINER, null);
        StatusUtil.updateWaveEndStatus(waveId, WAVE_STATUS.WORK_STAGE, null);
    }

    @Deprecated
    private void updateMoveTaskInfo(StockWaveResultVO waveResult, StockWaveResultDetailVO bill) {
        String warehouseId = waveResult.getWarehouseId();
        String enterpriseId = waveResult.getEnterpriseId();
        String opUser = waveResult.getOpUser();
        String opUserId = waveResult.getOpUserId();
        List<StockWaveItemDetail> itemDetails = bill.getItemDetails();
        for (StockWaveItemDetail item : itemDetails) {
            List<StockWaveReplenishTaskVO> replenishTaskList = item.getReplenishTaskList();
            logger.info("更新补货任务表");
            replenishTaskList.forEach(replenishTask -> {
                String billDId = item.getLineBillId();
                String itemId = item.getItemId();
                String billId = bill.getBillId();
                logger.info("更新补货任务表,billId={} ,商品id={}", billId, itemId);
                String srcLocatorId = replenishTask.getSrcWarehouseLocatorId();
                String destLocatorId = replenishTask.getDestWarehouseLocatorId();
                BigDecimal pieceQuantity = replenishTask.getPieceQuantity();
                MoveTaskDO moveTaskDO = new MoveTaskDO();
                moveTaskDO.setItemId(itemId);
                moveTaskDO.setBillId(billId);
                String moveTaskId = String.valueOf(SnowFlakeUtils.getId(dataCenterId,machineId));
                moveTaskDO.setMoveTaskId(moveTaskId);
                // 状态0,创建
                moveTaskDO.setStatus("0");
                moveTaskDO.setWarehouseId(warehouseId);
                moveTaskDO.setDestWarehouseLocatorId(destLocatorId);
                moveTaskDO.setSrcWarehouseLocatorId(srcLocatorId);
                moveTaskDO.setQty(pieceQuantity);
                moveTaskDO.setSrcWarehouseAreaId(replenishTask.getSrcWarehouseAreaId());
                moveTaskDO.setSrcWarehouseAreaNum(replenishTask.getSrcWarehouseAreaCode());
                moveTaskDO.setDestWarehouseAreaId(replenishTask.getDestWarehouseAreaId());
                moveTaskDO.setDestWarehouseAreaNum(replenishTask.getDestWarehouseAreaCode());
                moveTaskDO.setBldgId(replenishTask.getBuildingId());
                moveTaskDO.setBldgName(replenishTask.getBuildingName());
                UserDTO userDTO = new UserDTO();
                userDTO.setId(opUserId);
                userDTO.setEnterpriseId(enterpriseId);
                userDTO.setUsername(opUser);
                ModelUtils.setBaseInfo(moveTaskDO, ModelUtils.OpType.INSERT, userDTO);
                moveTaskMapper.save(moveTaskDO);

                // 更新补货表与库存表关系
                Map<String, Object> map = MapUtils.put("locatorId", destLocatorId).put("billDId", billDId).put("type", 1).getMap();
                List<WaveStorageDO> byMap = waveStorageMapper.findByMap(map, WaveStorageDO.class);
                if (byMap.size() != 1) {
                    logger.error("找不到对应的库存预占订单");
                } else {
                    WaveStorageDO storageDO = byMap.get(0);
                    storageDO.setMoveTaskId(moveTaskId);
                    waveStorageMapper.update(storageDO);
                }
            });
        }
    }

    @Deprecated
    private boolean updateBillStatus(StockWaveResultDetailVO bill, String billId, WaveBillDO billDo) {
        if (bill.isSuccess()) {
            logger.info("更新订单状态");
            billDo.setStatus(BILL_STATUS.POSITIONED);
            billMapper.updateFields(billDo, FieldSelector.newInstance().include("status"));
        } else {
            String errorMsg = bill.getErrorMsg();
            logger.error("库存预占失败,billID={}，errorMsg :{} ", billId, errorMsg);
            billDo.setStatus(BILL_STATUS.POSITION_ERROR);
            billDo.setErrorMsg(errorMsg);
            billMapper.updateFields(billDo, FieldSelector.newInstance().include("status").include("errorMsg"));
            return true;
        }
        return false;
    }

    private void updateLocators(StockWaveResultVO waveResult, StockWaveResultDetailVO bill, String billId) {
        List<StockWaveItemDetail> itemDetails = bill.getItemDetails();
        List<WaveStorageDO> saveResults = new ArrayList<>();
        for (StockWaveItemDetail item : itemDetails) {
            String billDId = item.getLineBillId();
            // 更新库位信息
            updateLocatorInfo(billDId, billId, saveResults, item, waveResult);
        }
        if (saveResults.size() > 0) {
            // 保存库存预占时，先更新是否是功能库房 - by Huanglh 2022-01-20
            // waveStorageMapper.saveBatch(saveResults);
            this.waveStorageUpdateIsFunctionRoomSaveBatch(saveResults);
        } else {
            logger.error("没有库位");
        }
    }

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

    /**
     * 组装预占数据
     * @param billDId 单据行ID
     * @param billId 单据ID
     * @param saveResults 要组装的预占数据集合
     * @param item 波次商品详情
     * @param waveResult 波次结果信息
     */
    private void updateLocatorInfo(String billDId, String billId, List<WaveStorageDO> saveResults, StockWaveItemDetail item, StockWaveResultVO waveResult) {
        List<StockWaveLocatorVO> pickLocators = item.getPickLocators();
        String waveId = waveResult.getWaveId();
        String enterpriseId = waveResult.getEnterpriseId();
        String warehouseId = waveResult.getWarehouseId();
        String itemId = item.getItemId();
        String opUser = waveResult.getOpUser();
        String opUserId = waveResult.getOpUserId();
        // 设置用户信息VO
        UserDTO userDTO = new UserDTO();
        userDTO.setId(opUserId);
        userDTO.setEnterpriseId(enterpriseId);
        userDTO.setUsername(opUser);
        if (pickLocators.isEmpty()) {
            logger.error("订单{}没有生成库位预占表", billId);
        }

        for (StockWaveLocatorVO locator : pickLocators) {
            WaveStorageDO waveStorageDO = new WaveStorageDO();
            waveStorageDO.setId(String.valueOf(SnowFlakeUtils.getId()));
            waveStorageDO.setWarehouseId(warehouseId);
            BigDecimal quantity = locator.getQuantity();
            // 最小单位数量 106 =1*100 + 6 1: 整箱数，6零件数
            waveStorageDO.setEnterpriseId(enterpriseId);
            waveStorageDO.setQty(quantity);
            waveStorageDO.setWaveId(waveId);
            waveStorageDO.setBillId(billId);
            waveStorageDO.setBillDId(billDId);
            waveStorageDO.setItemId(itemId);
            String locatorId = locator.getLocatorId();
            waveStorageDO.setLocatorId(locatorId);
            waveStorageDO.setBatchId(locator.getBatchId());
            waveStorageDO.setBatchNum(locator.getBatchNum());
            String warehouseAreaCode = locator.getWarehouseAreaCode();
            waveStorageDO.setWarehouseAreaCode(warehouseAreaCode);
            waveStorageDO.setProductionDate(locator.getBatchProductionDate());
            waveStorageDO.setExpiredDate(locator.getBatchExpiredDate());
            waveStorageDO.setShelfLifeDays(locator.getShelfLifeDays());
            waveStorageDO.setLocatorType(locator.getWarehouseLocatorType());
            waveStorageDO.setPickModel(locator.getPickModel());
            waveStorageDO.setPickPieceGroup(locator.getSplitGroup());
            waveStorageDO.setOutIndex(locator.getOutIndex());
            waveStorageDO.setLocatorCode(locator.getLocatorCode());
            waveStorageDO.setType(0);
            waveStorageDO.setItemPlaceType(locator.getItemPlaceType());
            waveStorageDO.setUom(locator.getUom());
            waveStorageDO.setFloorNum(locator.getFloorNum());

            waveStorageDO.setBldgId(locator.getBuildingId());
            String buildingName = locator.getBuildingName();
            waveStorageDO.setBldgName(buildingName);
            String warehouseAreaCategory = locator.getWarehouseAreaCategory();
            waveStorageDO.setAreaCategory(warehouseAreaCategory);
            String manufacturerLotNum = locator.getManufacturerLotNum();
            waveStorageDO.setManufactureLotNum(manufacturerLotNum);
            WaveBillDDO billD = billDMapper.findById(billDId, WaveBillDDO.class);
            BigDecimal volume = billD.getVolume();
            if (volume!=null && quantity!=null) {
                BigDecimal totalVolume = volume.multiply(quantity);
                waveStorageDO.setVolume(totalVolume);
            }
            waveStorageDO.setExpiredDateShow(locator.getBatchExpiredDateShow());
            waveStorageDO.setProductionDateShow(locator.getBatchProductionDateShow());
            ModelUtils.setBaseInfo(waveStorageDO, ModelUtils.OpType.INSERT, userDTO);

            // 0未组包
            waveStorageDO.setStatus("0");
            // 设置库存明细容器值
            waveStorageDO.setContainerCode(locator.getContainerCode());
            waveStorageDO.setContainerCodeQty(locator.getContainerCodeQty());
            saveResults.add(waveStorageDO);
        }
    }

    /**
     * 根据策略找订单集货区
     * @param waveId 波次ID
     * @param billGroupId 订单分组ID
     * @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> workStageRuntimeDOList = 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();
            logger.info("可用集货区列表:{}", JSONUtil.toJsonStr(searchResults));
            // 算法计算集货区挑选结果,算法入参：可用集货区集合,件数,订单
            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());
                workStageRuntimeDOList.add(info);
            }
        }
        logger.error("订单：{} 集货区结果为：{}", billGroupId, JSON.toJSONString(workStageRuntimeDOList));
        return workStageRuntimeDOList;
    }

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

        String shipLineId = billDO.getShipLineId();
        workStageContext.addDefaultSearchCondition("warehouseId", warehouseId);
        // 收货方式
        if (StringUtils.isBlank(billDO.getReceiveWay())) {
            throw new BizException("提货方式为空无法分配集货区!");
        }
        /**
         * 提货方式（河南省医药需核实是否是到货方式字段）
         *  1：集团连锁
         *  2：市外配送
         *  3：市内配送
         *  4：托运
         *  5：自提
         */
        if (null != billDO.getReceiveWay()) {
            if (billDO.getReceiveWay().equals("1")) {
                workStageContext.addDefaultSearchCondition("deliveryType", "集团连锁");
            } else if (billDO.getReceiveWay().equals("2")) {
                workStageContext.addDefaultSearchCondition("deliveryType", "市外配送");
            } else if (billDO.getReceiveWay().equals("3")) {
                workStageContext.addDefaultSearchCondition("deliveryType", "市内配送");
            }else if (billDO.getReceiveWay().equals("4")) {
                workStageContext.addDefaultSearchCondition("deliveryType", "托运");
            }else if (billDO.getReceiveWay().equals("5")) {
                workStageContext.addDefaultSearchCondition("deliveryType", "自提");
            }
        }
        // 配送方向
        if (StringUtils.isBlank(shipLineId)) {
            throw new BizException("配送方向为空无法分配集货区!");
        }
        workStageContext.addDefaultSearchCondition("segment1", shipLineId);
        return workStageContext;
    }

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

    private Strategy getStrategy(String wareHouseId, String cargoOwnerId, String enterpriseId) {
        StrategyResource strategyClient = SpringUtils.getBeansByType(StrategyResource.class);
        RestMessage<StrategyDTO> result = strategyClient.getStrategyDTO(enterpriseId, "ShippingArea", cargoOwnerId, wareHouseId);
        if (result == null || !result.isSuccess() || result.getData() == null) {
            logger.error("enterpriseId: {},category :{} cargoOwnerId :{},warehouseId :{}");
            throw new BizException("未找到有效的策略");
        }
        try {
            Strategy strategy = result.getData().createStrategy();
            return strategy;
        } catch (IllegalAccessException | InstantiationError | ClassNotFoundException | InstantiationException e) {
            throw new BizException("未找到有效的策略");
        }
    }

    @Override
    public long releaseWorkStage(String billGroupId) {
        if (StringUtils.isBlank(billGroupId)) {
            throw new ParameterException("订单编号不能为空");
        }
        return workStageMapper.updateStatus(billGroupId);
    }

    @Override
    @Flow(name = "集货区转移", category = "出库/波次管理/集货区转移", index = 8, description = "保存数据到WMS_WORK_STAGE_RUNTIME表，同时根据billGroupId，buildingCode删除集货区表数据")
    public RestMessage<Boolean> transfer(WorkStageTransferDTO workStageTransferDTO) {
        String billGroupId = workStageTransferDTO.getBillGroupId();
        String srcBldgCode = workStageTransferDTO.getSrcBldgCode();
        UserDTO userDTO = userTemplate.getUserDTO();
        Map<String, Object> map = MapUtils.put("billGroupId", billGroupId).put("buildingCode", srcBldgCode).getMap();
        List<WorkStageRuntimeDO> stages = workStageMapper.findByMap(map, WorkStageRuntimeDO.class);
        AssertUtils.isNotEmpty(stages, "暂存区不能为空");
        BigDecimal totalCaseQty = stages.stream().map(WorkStageRuntimeDO::getCaseQty).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
        String destBldgCode = workStageTransferDTO.getDestBldgCode();
        String stageCode = workStageTransferDTO.getStageCode();
        String stageId = workStageTransferDTO.getStageId();
        WorkStageRuntimeDO workStageRuntimeDO = stages.get(0);
        workStageRuntimeDO.setStageId(stageId);
        workStageRuntimeDO.setBillGroupId(billGroupId);
        workStageRuntimeDO.setStageCode(stageCode);
        workStageRuntimeDO.setCaseQty(totalCaseQty);
        workStageRuntimeDO.setBuildingCode(destBldgCode);
        ModelUtils.setBaseInfo(workStageRuntimeDO, ModelUtils.OpType.INSERT, userDTO);
        workStageMapper.deleteByMap(map, WorkStageRuntimeDO.class);
        workStageMapper.save(workStageRuntimeDO);
        return RestMessage.doSuccess(Boolean.TRUE);
    }

    /**
     * 货主转换，保存商品预占
     * @param waveId 波次ID
     * @param vo 货主转换VO
     * @param userDTO 用户对象
     */
    @Transactional
    public void handle2(String waveId, CargoOwnerChangeVO vo,UserDTO userDTO){
        String enterpriseId = userDTO.getEnterpriseId();
        String warehouseId = vo.getWarehouseId();
        List<WaveBillDO> billList=billMapper.findByMap(MapUtils.put("waveId",waveId).put("warehouseId",warehouseId).getMap(),WaveBillDO.class);
        WaveBillDO bill = billList.get(0);
        String billId = bill.getId();
        BillUtils.updateStatus(billId, BILL_STATUS.POSITIONED);
        List<WaveBillDDO> billDList = billDMapper.findByMap(MapUtils.put("billId",billId).put("enterpriseId",enterpriseId).getMap(),WaveBillDDO.class);
        Map<String,WaveBillDDO> billDMap = billDList.stream().collect(Collectors.toMap(WaveBillDDO::getItemId, a -> a, (key1, key2) -> key1));

        waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.COMPLETED).getMap(), WaveDO.class);
        StatusUtil.updateWaveStartStatus(waveId, WAVE_STATUS.COMPLETED, null);
        StatusUtil.updateWaveEndStatus(waveId, WAVE_STATUS.COUNT_CONTAINER, null);
        List<WaveStorageDO> saveResults = new ArrayList<>();
        List<WaveStorageVO> storageList = vo.getList();
        String [] batchIds = storageList.stream().map(WaveStorageVO::getBatchId).toArray(String []::new);
        RestMessage<List<BatchNumberListVO>> restMessage1 = inventoryResource.getBatchNumberBasicInfo(batchIds);
        if (!restMessage1.isSuccess()) {
            throw new PrologException("查询批次库存失败！");
        }
        List<BatchNumberListVO> batchNumberListList = restMessage1.getData();
        Map<String,BatchNumberListVO> batchNumberMap = batchNumberListList.stream().collect(Collectors.toMap(BatchNumberListVO::getBatchId, a -> a, (key1, key2) -> key1));

        String [] locatorIds = storageList.stream().map(WaveStorageVO::getLocatorId).toArray(String []::new);
        RestMessage<List<WarehouseLocatorBasicInfo>> restMessage2 = warehouseResource.getLocationBasicInfos(locatorIds);
        if (!restMessage2.isSuccess()) {
            throw new PrologException("查询货位信息失败！");
        }
        List<WarehouseLocatorBasicInfo> locatorList = restMessage2.getData();
        Map<String,WarehouseLocatorBasicInfo> locatorMap = locatorList.stream().collect(Collectors.toMap(WarehouseLocatorBasicInfo::getWarehouseLocatorId, a -> a, (key1, key2) -> key1));

        for (WaveStorageVO storageVO : storageList) {
            WaveStorageDO waveStorageDO = new WaveStorageDO();
            String itemId = null;
            if (batchNumberMap.containsKey(storageVO.getBatchId())) {
                BatchNumberListVO batchNumberListVO = batchNumberMap.get(storageVO.getBatchId());
                waveStorageDO.setId(String.valueOf(SnowFlakeUtils.getId()));
                waveStorageDO.setBatchNum(batchNumberListVO.getBatchNum());
                waveStorageDO.setProductionDate(batchNumberListVO.getBatchProductionDate());
                waveStorageDO.setExpiredDate(batchNumberListVO.getBatchExpiredDate());
                waveStorageDO.setShelfLifeDays(new BigDecimal(batchNumberListVO.getShelfLifeDays()));
                waveStorageDO.setManufactureLotNum(batchNumberListVO.getManufacturerLotNum());
                waveStorageDO.setUom(batchNumberListVO.getCaseUom());
                waveStorageDO.setExpiredDateShow(batchNumberListVO.getBatchExpiredDateShow());
                waveStorageDO.setProductionDateShow(batchNumberListVO.getBatchProductionDateShow());
                itemId = batchNumberListVO.getItemId();
            }
            if (locatorMap.containsKey(storageVO.getLocatorId())) {
                WarehouseLocatorBasicInfo locator = locatorMap.get(storageVO.getLocatorId());
                String warehouseAreaCode = locator.getWarehouseAreaCode();
                waveStorageDO.setWarehouseAreaCode(warehouseAreaCode);
                waveStorageDO.setLocatorType(locator.getWarehouseLocatorType());
                waveStorageDO.setPickPieceGroup(locator.getSplitGroup());
                waveStorageDO.setOutIndex(locator.getOutIndex());
                waveStorageDO.setLocatorCode(locator.getWarehouseLocatorCode());
                waveStorageDO.setItemPlaceType(locator.getItemPlaceType());
                waveStorageDO.setFloorNum(locator.getFloorNum());
                waveStorageDO.setBldgId(locator.getBldgId());
                waveStorageDO.setBldgName(locator.getBldgName());
                waveStorageDO.setAreaCategory(locator.getWarehouseAreaCategory());
            }

            BigDecimal quantity = storageVO.getQty();
            if (itemId != null && billDMap.containsKey(itemId)) {
                WaveBillDDO waveBillDDO = billDMap.get(itemId);
                waveStorageDO.setBillDId(waveBillDDO.getId());
                waveStorageDO.setItemId(itemId);
                BigDecimal volume = waveBillDDO.getVolume();
                if (volume != null) {
                    BigDecimal totalVolume = volume.multiply(quantity);
                    waveStorageDO.setVolume(totalVolume);
                }
            }
            waveStorageDO.setWarehouseId(warehouseId);
            waveStorageDO.setEnterpriseId(enterpriseId);
            waveStorageDO.setQty(quantity);
            waveStorageDO.setWaveId(waveId);
            waveStorageDO.setBillId(billId);
            String locatorId = storageVO.getLocatorId();
            waveStorageDO.setLocatorId(locatorId);
            waveStorageDO.setBatchId(storageVO.getBatchId());
            waveStorageDO.setType(0);
            ModelUtils.setBaseInfo(waveStorageDO, ModelUtils.OpType.INSERT, userDTO);
            // 0 未组包
            waveStorageDO.setStatus("0");
            saveResults.add(waveStorageDO);
        }
        // 保存库存预占时，先更新是否是功能库房 - by Huanglh 2022-01-20
        // waveStorageMapper.saveBatch(saveResults);
        this.waveStorageUpdateIsFunctionRoomSaveBatch(saveResults);
        try {
            // 生成自动出库数据
            List<TaskParamsVO> waveList = new ArrayList<>();
            TaskParamsVO taskParamsVO = new TaskParamsVO();
            taskParamsVO.setEnterpriseId(enterpriseId);
            taskParamsVO.setWarehouseId(vo.getWarehouseId());
            taskParamsVO.setWaveId(waveId);
            waveList.add(taskParamsVO);
            packService.createPickTaskSpecial(waveList);
        } catch (Exception e) {
            waveMapper.updateMapById(waveId, MapUtils.put("status", WAVE_STATUS.ERROR).getMap(), WaveDO.class);
            logger.error("货主转换-计算容器任务失败 wave id={}", waveId, e);
        }
    }

    public void waveStorageUpdateIsFunctionRoomSaveBatch(List<WaveStorageDO> storageDOS) {
        // 根据货位id获取货位所在库房是否是功能库房 - by Huanglh 2022-01-20
        // key-货位id，value-是否是功能库房(0 不是,1 是)
        Map<String, String> isFunctionRoomMap = new HashedMap<>();
        List<String> locatorIds = storageDOS.stream().map(WaveStorageDO::getLocatorId).collect(Collectors.toList());
        RestMessage<Map<String, String>> restMsg = warehouseResource.getIsFunctionRoomByLocatorId(locatorIds);
        if (ObjectUtils.isNull(restMsg) || !restMsg.isSuccess()) {
            logger.error("调用接口获取货位所在库房是否是功能库房失败");
        } else {
            isFunctionRoomMap = restMsg.getData();
        }

        // 普通数据
        List<WaveStorageDO> waveStorageDOList = new ArrayList<>();
        // AGV数据
        List<WaveStorageDO> agvWaveStorageDOList = new ArrayList<>();
        for (WaveStorageDO storageDO : storageDOS) {
            String locatorId = storageDO.getLocatorId();
            storageDO.setIsFunctionRoom(isFunctionRoomMap.containsKey(locatorId) ? isFunctionRoomMap.get(locatorId) : null);
            // MJK和AGVL货格的数据要汇总,AGVZ托盘库的不需要,自带托盘号
            if (!StringUtils.isBlank(storageDO.getAreaCategory()) && (storageDO.getAreaCategory().contains("MJK") || storageDO.getAreaCategory().contains("AGVL"))) {
                storageDO.setContainerCode("N/A");
                agvWaveStorageDOList.add(storageDO);
            } else {
                waveStorageDOList.add(storageDO);
            }
        }

        try {
            if (CollectionUtil.isNotEmpty(waveStorageDOList)) {
                waveStorageMapper.saveBatch(waveStorageDOList);
            }
            if (CollectionUtil.isNotEmpty(agvWaveStorageDOList)) {
                // 按照同品,同批,同货位,同订单进行分组
                Map<String, List<WaveStorageDO>> storageMap = agvWaveStorageDOList.stream().collect(Collectors.groupingBy(x -> StrUtil.join(",", x.getItemId(), x.getBillDId(), x.getBatchId(), x.getLocatorId())));
                for (Map.Entry<String, List<WaveStorageDO>> stockPres : storageMap.entrySet()) {
                    WaveStorageDO waveStorage = new WaveStorageDO();
                    BigDecimal sumQty = BigDecimal.ZERO;
                    // 遍历预占
                    for (WaveStorageDO waveStorageDO : stockPres.getValue()) {
                        if (StringUtils.isBlank(waveStorage.getLocatorId())) {
                            BeanUtils.copyProperties(waveStorageDO,waveStorage);
                        }
                        sumQty = sumQty.add(waveStorageDO.getQty());
                    }
                    waveStorage.setQty(sumQty);
                    waveStorage.setId(String.valueOf(SnowFlakeUtils.getId()));
                    waveStorageMapper.save(waveStorage);
                }
            }
        } catch (Exception e) {
            logger.error("WMS_WAVE_STORAGE主键重复,再次保存数据!");
        }
    }

    /**
     * 非全物料单据，赠品自动拣货确认扣减库存
     * @param waveId
     */
    @Transactional
    public void handleGift(String waveId) {
        log.info("波次下发完成，物料库自动拣货扣减库存开始：{}",waveId);
        new Thread(()-> {

            try {
                List<WaveStorageDO> waveStorageDOS = waveStorageMapper.getGiftItemByWaveId(waveId);
                log.info("需要自动扣减库存的波次预占明细：{}",JSON.toJSONString(waveStorageDOS));
                for (WaveStorageDO waveStorageDO : waveStorageDOS) {
                    handleGiftIteminvStockConfirm(waveStorageDO);
                }
                //更新wms_pick_task_d的状态
                List<String> waveStorageIds = waveStorageDOS.stream().map(WaveStorageDO::getId).distinct().collect(Collectors.toList());
                if(waveStorageIds == null || waveStorageIds.size() <= 0){
                    throw new BizException("波次对应的波次预占为空!");
                }
                pickTaskDMapper.updateByWaveStorageIds(waveStorageIds);
                //更新wms_pick_task_h状态
                Criteria crd = Criteria.forClass(PickTaskD.class);
                Restriction rd1 = Restrictions.in("waveStorageId", waveStorageIds.toArray());
                crd.setRestriction(Restrictions.and(rd1));
                List<PickTaskD> pickTaskDS = pickTaskDMapper.findByCriteria(crd);
                if(pickTaskDS == null || pickTaskDS.size() <= 0){
                    throw new BizException("查询任务明细失败!");
                }
                List<String> pickTaskIds = new ArrayList<>();
                for (PickTaskD pickTaskD : pickTaskDS) {
                    Criteria crd1 = Criteria.forClass(PickTaskD.class);
                    Restriction rtd1 = Restrictions.eq("pickTaskId", pickTaskD.getPickTaskId());
                    Restriction rtd2 = Restrictions.ne("pickTaskDId", pickTaskD.getPickTaskDId());
                    Restriction rtd3 = Restrictions.lt("flowStatus", '7');
                    crd1.setRestriction(Restrictions.and(rtd1,rtd2,rtd3));
                    long count = pickTaskHMapper.findCountByCriteria(crd1);
                    if(count <= 0){
                        pickTaskIds.add(pickTaskD.getPickTaskId());
                    }
                }
                if(pickTaskIds.size() > 0){
                    Criteria crh = Criteria.forClass(PickTaskH.class);
                    Restriction rh = Restrictions.in("pickTaskId", pickTaskIds.toArray());
                    crh.setRestriction(Restrictions.and(rh));
                    pickTaskHMapper.updateMapByCriteria(MapUtils.put("status","10").getMap(),crh);
                }

            }catch (Exception e) {
                log.error("物料库扣减扣库存失败：{}", e.getMessage());
            }
        }).start();

    }

    private void handleGiftIteminvStockConfirm(WaveStorageDO waveStorageDO){
        log.info("开始扣减库存");
        InvStockPartCheckOut invStockCheck = new InvStockPartCheckOut();
        invStockCheck.setContainerCode(waveStorageDO.getContainerCode());
        invStockCheck.setQty(waveStorageDO.getQty());
        invStockCheck.setLocatorId(waveStorageDO.getLocatorId());
        invStockCheck.setItemId(waveStorageDO.getItemId());
        invStockCheck.setLineBillIds(new String[]{waveStorageDO.getBillDId()});
        invStockCheck.setBatchNum(waveStorageDO.getBatchNum());
        RestMessage restMessage = invClient.outPartStockCheck(invStockCheck);
        log.info("请求参数：{}", JSONObject.toJSONString(invStockCheck));
        if (!restMessage.isSuccess()) {
            log.info("扣减库存失败: " + restMessage.getMessage() +
                    String.format("，入参为：[仓库：%s][订单编号：%s]", waveStorageDO.getWarehouseId(), JSONObject.toJSONString(invStockCheck)));
        }
        log.info("扣减库存成功");
    }


}
