package com.bailuntec.job;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.bailuntec.domain.constant.CommonConstant;
import com.bailuntec.domain.constant.Constant;
import com.bailuntec.domain.dto.CatagoryDTO;
import com.bailuntec.domain.dto.SalesVolumeAvgDTO;
import com.bailuntec.domain.dto.SalesVolumeDTO;
import com.bailuntec.domain.entity.*;
import com.bailuntec.domain.enumerate.PlatformType;
import com.bailuntec.domain.example.*;
import com.bailuntec.mapper.*;
import com.bailuntec.support.PointJob;
import com.bailuntec.utils.DigestUtils;
import com.bailuntec.utils.OkHttpUtil;
import com.bailuntec.utils.PropertiesUtil;
import com.bailuntec.utils.SessionUtil;
import com.dangdang.ddframe.job.api.ShardingContext;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLDecoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

@Slf4j
public class AutoTurnoverJob extends PointJob {
    private AutoSalesServiceImpl autoSalesServiceImpl = new AutoSalesServiceImpl();
    private PropertiesUtil propertiesUtil = PropertiesUtil.getInstance("const");
    private OkHttpClient client = OkHttpUtil.getInstance();

    @Override
    public void executeJob(ShardingContext shardingContext, JobPointLog jobPointLog) {
        long count = getTotalCount();
        int totalPage = getTotalPage(count, jobPointLog.getPageSize());
        /*
         * 根据不同分片
         * 再分页去处理数据
         */
        int shardingSize = totalPage % shardingContext.getShardingTotalCount() == 0? totalPage / shardingContext.getShardingTotalCount() : totalPage / shardingContext.getShardingTotalCount() + 1;
        if (shardingContext.getShardingItem() + 1 < shardingContext.getShardingTotalCount()) {
            totalPage = shardingSize * (shardingContext.getShardingItem() + 1);
        }
        if (totalPage > 0) {
            int page = 0;
            if  (shardingContext.getShardingItem() > 0) {
                page = shardingSize * shardingContext.getShardingItem();
            }
            String queryTime = DateTimeFormatter.ofPattern(CommonConstant.DATE_FORMAT).format(LocalDate.now());
            do {
                List<DcBaseStock> dcBaseStockList = null;//分页查SKU_仓库
                try {
                    DcBaseStockMapper baseStockMapperFor = SessionUtil.getSession().getMapper(DcBaseStockMapper.class);
                    //这里是根据id排序,方法名没改了
                   dcBaseStockList = baseStockMapperFor.listOrderByFba(page * jobPointLog.getPageSize(), jobPointLog.getPageSize());
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("Mybatis操作DB分页查询失败", e);
                } finally {
                    SessionUtil.closeSession();
                }
                if (dcBaseStockList != null && dcBaseStockList.size() > 0) {
                    for (DcBaseStock dcBaseStock : dcBaseStockList) {
                        try {
                            autoTurnoverFromStock(queryTime, dcBaseStock);
                        } catch (Exception e) {
                            //错误记录到日志表, 任务跑完了再跑一次错误SKU
                            try {
                                DcAutoExceptionMapper dcAutoExceptionMapper = SessionUtil.getSession().getMapper(DcAutoExceptionMapper.class);
                                DcAutoException dcAutoException = new DcAutoException(dcBaseStock.getBailunSku(),dcBaseStock.getWarehouseCode(),e.toString());
                                dcAutoExceptionMapper.insertSelective(dcAutoException);
                            } catch (Exception ex) {
                                log.error("保存错误SKU失败,sku="+ dcBaseStock.getBailunSku() + ",仓库=" + dcBaseStock.getWarehouseCode());
                            } finally {
                                SessionUtil.closeSession();
                            }
                        }
                    }
                }
                page++;
            } while (page <= totalPage);
        }

    }

    private long getTotalCount() {
        try {
            DcBaseStockMapper baseStockMapper = SessionUtil.getSession().getMapper(DcBaseStockMapper.class);
            return baseStockMapper.countByExample(DcBaseStockExample.newAndCreateCriteria().example());
        } catch (Exception e) {
            throw new RuntimeException("Mybatis操作DB查询总页数失败", e);
        } finally {
            SessionUtil.closeSession();
        }
    }

    /**
     * 基于库存
     * 计算自动周转
     *
     * @param queryTime
     * @throws Exception
     */
    public void autoTurnoverFromStock(String queryTime, DcBaseStock dcBaseStock) throws Exception {
        String bailunSku = dcBaseStock.getBailunSku();
        String warehouseCode = dcBaseStock.getWarehouseCode();
        DcAutoSales dcAutoSales = null;
        try {
            DcAutoSalesMapper autoSalesMapper = SessionUtil.getSession().getMapper(DcAutoSalesMapper.class);
            SalesVolumeDTO salesVolumeDTO = autoSalesMapper.getSalesVolumeDTO(queryTime, bailunSku, warehouseCode);
            if (StringUtils.isEmpty(salesVolumeDTO.getHistorySales().trim())) {
                DcAutoSalesMapper dcAutoSalesMapper = SessionUtil.getSession().getMapper(DcAutoSalesMapper.class);
                dcAutoSales = new DcAutoSales(bailunSku, warehouseCode);
                dcAutoSalesMapper.upsertSelective(dcAutoSales);
            } else {
                dcAutoSales = autoSalesServiceImpl.forecastSalesBySalesVolumeDTO(salesVolumeDTO);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB失败", e);
        } finally {
            SessionUtil.closeSession();
        }
        autoTurnoverUseDcAutoSales(bailunSku, warehouseCode, dcAutoSales, dcBaseStock);
    }

    /*
     * 将历史销量,dcAutoSales
     * 根据历史销量拟合的销量函数曲线,dcAutoSales
     * 库存信息传入,dcBaseStock
     * 计算自动周转
     */
    private void autoTurnoverUseDcAutoSales(String bailunSku, String warehouseCode, DcAutoSales dcAutoSales, DcBaseStock dcBaseStock) throws Exception {
        LocalDate recordTime = LocalDate.now();
        DcAutoTurnover dcAutoTurnover = new DcAutoTurnover();
        dcAutoTurnover.setBailunSku(bailunSku);
        dcAutoTurnover.setWarehouseCode(warehouseCode);
        /*
         * 通过公式计算周转天数
         * 1. 拿到供应商交期-turnoverSku和百伦分类Id-bailunFirstLevelCatagoryId
         * 2. 获取调拨头程 + 调拨打包 + 海外仓入库天数 的配置 dcAutoConfigDelivery
         * 3. 拿到仓库名称 和 仓库类型(可选) turnoverWarehouse
         * 周转天数计算公式:
         * 供应链长度 = 周转天数 = 供应商交期 + 入库时间(2天质检入库时间) + 调拨头程  + 调拨打包  + 海外仓入库
         */
        DcBaseSku turnoverSku = null;
        DcBaseWarehouse dcBaseWarehouse = null;
        //从采购平均指标,仓库平均指标拿周转天数, 拿不到的或者数值异常的, 才按上面的来
        DcAveragePurchase dcAveragePurchase = null;
        DcAverageWarehouse dcAverageWarehouse = null;
        //是否先款后货? 先款后货供应链长度加一天paymentBeforeDelivery = 1;
        Integer paymentBeforeDelivery = null;
        try {
            DcBaseSkuMapper baseSkuMapper = SessionUtil.getSession().getMapper(DcBaseSkuMapper.class);
            turnoverSku = baseSkuMapper.selectOneByExample(DcBaseSkuExample.newAndCreateCriteria().andBailunSkuEqualTo(bailunSku).example());
            DcBaseWarehouseMapper baseWarehouseMapper = SessionUtil.getSession().getMapper(DcBaseWarehouseMapper.class);
            dcBaseWarehouse = baseWarehouseMapper.selectOneByExample(DcBaseWarehouseExample.newAndCreateCriteria().andWarehouseCodeEqualTo(warehouseCode).example());
            DcAveragePurchaseMapper dcAveragePurchaseMapper = SessionUtil.getSession().getMapper(DcAveragePurchaseMapper.class);
            dcAveragePurchase = dcAveragePurchaseMapper.selectOneByExample(DcAveragePurchaseExample.newAndCreateCriteria().andBailunSkuEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).andSupplierIdEqualTo(turnoverSku.getSuppliersId()).example());
            DcAverageWarehouseMapper dcAverageWarehouseMapper = SessionUtil.getSession().getMapper(DcAverageWarehouseMapper.class);
            dcAverageWarehouse = dcAverageWarehouseMapper.selectOneByExample(DcAverageWarehouseExample.newAndCreateCriteria().andBailunSkuEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).example());
            //没有dcAveragePurchase这数据也要加一天
            if (dcAveragePurchase == null || (dcAveragePurchase.getPaymentType() != null && dcAveragePurchase.getPaymentType().equals(1))) {
                paymentBeforeDelivery = 1;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB失败", e);
        } finally {
            SessionUtil.closeSession();
        }
        if (dcBaseWarehouse != null) {
            dcAutoTurnover.setWarehouseName(dcBaseWarehouse.getWarehouseName());
            if (dcBaseWarehouse.getSystemFlag().toUpperCase().equals(PlatformType.FBA.value())) {
                dcAutoTurnover.setWarehouseName(dcBaseWarehouse.getBailunAccount());
            }
        }
        Integer bailunFirstLevelCatagoryId = getBailunCatagoryId(turnoverSku); //百伦第一级分类Id, 可以为null
        // 获取调拨头程 + 调拨打包 + 海外仓入库天数 的配置
        DcAutoConfigDelivery dcAutoConfigDelivery = getDcAutoConfigDelivery(bailunSku,warehouseCode, dcBaseWarehouse, bailunFirstLevelCatagoryId);
        //获取周转天数计算配置, 数据来源于.net同事计算的均值
        Integer turnoverDays = getTurnoverDelivery(turnoverSku,dcBaseWarehouse, dcAutoTurnover, dcAutoConfigDelivery, dcAveragePurchase, dcAverageWarehouse);
        if (paymentBeforeDelivery != null) {
            turnoverDays += paymentBeforeDelivery;
        }
        /*
         * 因为第一个周转期再补货已经来不及
         * 所以预测2倍周转期长度
         * 2倍周转期长度 < 31 的, 默认算31天, 但是生成的采购建议不拿2倍周转期以外的
         */
        Integer autoForecastDay = turnoverDays * Constant.TURNOVER_MULTIPLE < Constant.MIN_AUTO_FORECAST_DAY ? Constant.MIN_AUTO_FORECAST_DAY : turnoverDays * 2;
        /*
         * 初始化预测容器, 用来存放预测销量, 库存等数据
         * forecastInventoryList
         * forecastPurchaseInboundList
         * forecastTransferInboundList
         * forecastInboundList
         * forecastSalesList
         * forecastShortSupplyList
         */
        List<Integer> forecastPurchaseInboundList = new ArrayList<Integer>(autoForecastDay);
        List<Integer> forecastTransferInboundList = new ArrayList<Integer>(autoForecastDay);
        List<BigDecimal> forecastInventoryList = new ArrayList<BigDecimal>(autoForecastDay);
        List<Integer> forecastInboundList = new ArrayList<Integer>(autoForecastDay);
        List<String> forecastInboundRelationList = new ArrayList<String>(autoForecastDay);
        List<BigDecimal> forecastSalesList = new ArrayList<BigDecimal>(autoForecastDay);
        List<String> forecastSalesExplainList = new ArrayList<String>(forecastSalesList.size());
        List<BigDecimal> forecastShortSupplyList = new ArrayList<BigDecimal>(autoForecastDay);
        List<BigDecimal> forecastPurchaseAdvisedList = new ArrayList<BigDecimal>(autoForecastDay);
        List<BigDecimal> moqPurchaseAdvisedList = new ArrayList<BigDecimal>(autoForecastDay);
        initForecaseList(forecastInboundRelationList, forecastInventoryList, forecastPurchaseInboundList, forecastTransferInboundList, forecastInboundList, forecastSalesList, forecastShortSupplyList, autoForecastDay, forecastSalesExplainList);

        /*
         * 去销量配置表dc_auto_config_sales_upper_limit找销量最大值
         * 1. 根据仓库SKU找销量上限, 有就停止, 没有则2
         * 2. 根据仓库和分类找销量上限, 有就停止, 没有则3
         * 3. 根据倍数算上限
         * 4. 根据加权平均算上限
         */
        BigDecimal salesUpperLimit = calculateSalesUpperLimit(bailunSku, warehouseCode, bailunFirstLevelCatagoryId, dcAutoSales, JSON.toJSONString(forecastSalesList));


        /*
         * 线性回归算法: 过去30天销量	根据这个预测销量走势是上升还是下降，并量化到数值
         * 均值, 配置安全库存法: 销量上下限	根据过去30天销量，计算出合理的销量封顶值和销量最低值
         * 曲线拟合 :销量预测, 根据销量走势和上下限，计算出销量预测曲线, 如果历史销量包含0, 无法使用指数函数拟合
         */
        calculateForecastSales(salesUpperLimit, autoForecastDay, dcAutoSales, forecastSalesList);

        /*
         * 处理矫正信息
         * 主要是根据市场行情，人为的干预预测结果
         *  例如圣诞档期，圣诞之前销量会连续上涨，但是过了23号之后，就会急速下跌
         * 这种情况就要人工设定一条增益曲线，如果想放大预测，曲线值大于1，如果想缩减预测，曲线值小于1
         * 然后跟基本预测销量相乘，到到最终的预测销量
         */
        correctionForecastSales(recordTime, autoForecastDay, dcAutoSales, forecastSalesList);

        /*
         * 活动因素叠加
         * 主要是根据活动排期，人为干预销量预测结果W
         * 例如报Deal，时间两天，预估这两天因为deal会每天带来100单额外销量
         * 那按照日期把额外的销量配置出来，将预测销量跟额外销量相加得到最终销量
         */
        StringBuilder promotionsBuilder = promotionForecastSales(recordTime, autoForecastDay, dcAutoSales, forecastSalesList);


        Integer purchase = 0;//采购在途
        Integer transfer = 0;//调拨在途
        Integer outStock = 0 ;//缺货数
        DcMidTransit dcMidTransit = null;
        try {
            DcMidTransitMapper midTransitMapper = SessionUtil.getSession().getMapper(DcMidTransitMapper.class);
            dcMidTransit = midTransitMapper.selectOneByExample(DcMidTransitExample.newAndCreateCriteria().andBailunSkuEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).example());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB失败", e);
        } finally {
            SessionUtil.closeSession();
        }

        if (dcMidTransit != null) {
            purchase = dcMidTransit.getQuantityPurchase();
            transfer = dcMidTransit.getQuantityTransfer();
            outStock = dcMidTransit.getQuantityOutStock();
        }
        /*
         * 拿到预计到货时间在周转期内的采购单
         * 采购系统的预计到货只是到货时间
         * 还需要加上入库时长,
         * 且不同交通方式的入库时长不一致
         */
        ExpectedStorage(forecastInboundRelationList, purchase, transfer, recordTime, dcAutoTurnover, forecastPurchaseInboundList, forecastTransferInboundList, forecastInboundList);

        //通过SKU和仓库拿到实际库存
        Integer realInventory = (dcBaseStock != null && dcBaseStock.getUsableStock() != null) ? dcBaseStock.getUsableStock() : 0;
        /*
         * 通过SKU拿到供应商Id,
         * 再去dc_average_purchase拿到平均交期
         * 平均交期 - 供应商交期 > 0  就是平均逾期天数
         *
         * 平均逾期天数 * 加权平均销量(去最大值最小值)
         * 为安全库存--即防止逾期缓冲所需库存
         * 可以支持配置
         * 2019-04-22更新 不管逾期
         */
        BigDecimal totalSafeInventory = calculateSafeInventory(bailunSku, warehouseCode, bailunFirstLevelCatagoryId, dcAutoSales, JSON.toJSONString(forecastSalesList), dcAutoTurnover);

        Integer moq = turnoverSku.getMoq();
        /*
         * 预测库存 = 前一日库存 - 前一日销量(没有考虑实际缺货)
         *
         * 采购建议为周转期后那一天的销量 + 安全库存 - 预测库存 + 累计断货
         * 如果有库存, 优先抵消断货数, 再去跟销量对比
         */
        //通过SKU和仓库取预测销量
        Integer inventoryAvailableDays = 0;
        BigDecimal totalSales = BigDecimal.ZERO;//周转期总销量
        BigDecimal totalInbound = BigDecimal.ZERO;//周转期总预计入库
        BigDecimal moqDecimal = BigDecimal.valueOf(moq.longValue());
        List<Integer> historySalesList = JSON.parseObject(dcAutoSales.getHistorySalesDetails(), new TypeReference<List<Integer>>() {
        });


        /*
         * dcAutoConfigSkuWarehouse不存在或者其status=0都是监控状态
         * 库存为0 并且 最近14天日均销量为0
         * 已经提醒实际缺货的，预计缺货就不提醒了
         * 已经提醒实际冗余的，预计冗余就不提醒了
         * 停止监控的不提醒
         */
        Integer historyFourteenSales = 0;
        Integer historySevenSales = 0;
        Integer historyThreeSales = 0;
        Integer historyThirtySales = 0;
        for (int i = 0; i < 30; i++) {
            if (i < 3) {
                historyThreeSales += historySalesList.get(27 + i);
            }
            if (i < 7) {
                historySevenSales += historySalesList.get(23 + i);
            }
            if (i < 14) {
                historyFourteenSales += historySalesList.get(16 + i);
            }
            historyThirtySales  += historySalesList.get(i);
        }
        BigDecimal avgHistoryThreedaySales = BigDecimal.valueOf(historyThreeSales.longValue()).divide(Constant.BIGDECIMAL_THREE, 3, RoundingMode.HALF_EVEN);
        BigDecimal avgHistorySevendaySales = BigDecimal.valueOf(historySevenSales.longValue()).divide(Constant.BIGDECIMAL_SEVEN, 3, RoundingMode.HALF_EVEN);
        BigDecimal avgHistoryFourteendaySales = BigDecimal.valueOf(historyFourteenSales.longValue()).divide(Constant.BIGDECIMAL_FOURTEEN, 3, RoundingMode.HALF_EVEN);
        BigDecimal avgHistoryThirtydaySales = BigDecimal.valueOf(historyThirtySales.longValue()).divide(Constant.BIGDECIMAL_THIRTY, 3, RoundingMode.HALF_EVEN);
        /*
         * forecastFluctuationList 用于存放预计缺货 实际缺货 预计冗余  实际冗余的数据
         * dcAutoConfigSkuWarehouse 看是否监控状态
         * hasMonitorFluctuation 是否停止监控波动功能
         */
        List<DcAutoForecastFluctuation> forecastFluctuationList = new ArrayList<>(turnoverDays);
        DcAutoConfigSkuWarehouse dcAutoConfigSkuWarehouse = getAutoConfigSkuWarehouse(bailunSku, warehouseCode);
        boolean hasMonitorFluctuation = actualForecastFluctuations(dcAutoTurnover,recordTime, forecastSalesList, realInventory, totalSafeInventory, outStock, dcAutoConfigSkuWarehouse, avgHistoryFourteendaySales, avgHistoryThreedaySales, forecastFluctuationList);

        /*
         * 获取采购建议-FBA仓库
         *  实际缺货 + 总销量 - 总预计入库  + (安全库存 - 预测库存) + 周转天的销量
         */
        if (dcBaseWarehouse.getSystemFlag().toUpperCase().equals(PlatformType.FBA.value())) {
            /*
             * fba仓库新增一套规则
             * 1. fba缺货 销量需要变成0 因为没货就真的是0了
             * 2. 到货后，离缺货日期如果有N天， 每天预计销量比原来 递减7%   如果缺货了10天 10天后到货，也就是10天后的预计销量 （1-0.07）的10次方，但是这个只是预测
             * 3. 如果到货后有真实销量，迅速用真实销量的曲线替代这个7%递减的曲线，这个递减曲线的影响马上清0
             * 4. 通常缺货后，我们会用一个sku跟卖，这个sku销量会很高   我们的处理方法是，对这个sku做特殊销售规则 （这个人为做就行）
             */
            inventoryAvailableDays = fbaAdvise(hasMonitorFluctuation, forecastFluctuationList, recordTime, dcAutoTurnover, turnoverDays, autoForecastDay, forecastInventoryList, forecastInboundList, forecastSalesList, forecastShortSupplyList, forecastPurchaseAdvisedList, moqPurchaseAdvisedList, realInventory, totalSafeInventory, outStock, inventoryAvailableDays, totalSales, totalInbound, moqDecimal, historySalesList, forecastSalesExplainList);
        } else {
            /*
             * 获取采购建议-普通仓库
             *  实际缺货 + 总销量 - 总预计入库  + (安全库存 - 预测库存) + 周转天的销量
             */
            inventoryAvailableDays = commonAdvise(hasMonitorFluctuation, forecastFluctuationList, recordTime, dcAutoTurnover, turnoverDays, autoForecastDay, forecastInventoryList, forecastInboundList, forecastSalesList, forecastShortSupplyList, forecastPurchaseAdvisedList, moqPurchaseAdvisedList, realInventory, totalSafeInventory, outStock, inventoryAvailableDays, totalSales, totalInbound, moqDecimal);
        }

        /*
         * 自动下单管理的冗余-缺货等异常提醒
         */
        unusualFluctuation(dcAutoConfigSkuWarehouse, turnoverSku, warehouseCode, dcAutoTurnover, avgHistoryThreedaySales, forecastFluctuationList);

        //拿一下2倍周转期内的入库总数
        Integer quantityInbound = 0;
        for (int i1 = 0; i1 < Constant.TURNOVER_MULTIPLE * turnoverDays; i1++) {
            quantityInbound += forecastInboundList.get(i1);
        }
        /*
         * 集合数据持久化到DB
         */
        assignmentToForecastSales(bailunSku, warehouseCode, forecastSalesList);
        assignmentToForecastInventory(bailunSku, warehouseCode, realInventory, forecastInventoryList);
        assignmentToForecastShortSupply(bailunSku, warehouseCode, forecastShortSupplyList);
        assignmentToForecastInbound(bailunSku, warehouseCode, realInventory, forecastInboundList, forecastInboundRelationList);

        /*
         * 周转表加一个，备货天数的额外销量(建议数累加预测销量)
         * 如果备货天数小于周转天数, 没问题
         * 如果备货天数大于周转天数, 采购建议数都没生成那么多天, 取索引为turnoverDays的
         */
        Integer stockUpDays = getStockUpDays(warehouseCode);
        /*
         * 自动周转表的预测销量 要加上7天真实销量
         * 自动周转表的预测入库 要加上7天真实入库 - TODO
         * 自动周转表的预测库存 要加上7天真实库存 - TODO
         * 自动周转表的预测缺货数 要加上7天真实缺货数 - TODO
         * 计算过去7天，14天日均销量, 展示用
         * 计算1日,7日,14日平均预测销量, 展示用
         */
        BigDecimal avgForecastOnedaySales = forecastSalesList.get(0);
        BigDecimal avgForecastSevendaySales = BigDecimal.ZERO;
        BigDecimal avgForecastFourteendaySales = BigDecimal.ZERO;
        BigDecimal avgForecastTurnoverDaySales = BigDecimal.ZERO;
        BigDecimal stockUpSales = BigDecimal.ZERO;
        int forturnoverDays = turnoverDays < 14? 14 : turnoverDays;
        for (int i = 0; i < forturnoverDays; i++) {
            if (i < 7) {
                avgForecastSevendaySales = avgForecastSevendaySales.add(forecastSalesList.get(i));
                forecastSalesList.add(i, BigDecimal.valueOf(historySalesList.get(23 + i)));
                forecastSalesExplainList.add(i, "历史销量");
                forecastInboundList.add(i, 0);
                forecastInventoryList.add(i, BigDecimal.ZERO);
                forecastShortSupplyList.add(i, BigDecimal.ZERO);
            }
            if(i < 14) {
                avgForecastFourteendaySales = avgForecastFourteendaySales.add(forecastSalesList.get(i));
            }
            if (i < turnoverDays) {
                avgForecastTurnoverDaySales = avgForecastTurnoverDaySales.add(forecastSalesList.get(i));
            }
            //如果初始采购建议 <= 0, 那就也不用备货., stockUpDays - 1的原因是我们已经算了一天的预测销量了.
            if (i < stockUpDays - 1 && forecastPurchaseAdvisedList.get(0).compareTo(BigDecimal.ZERO) == 1) {
                stockUpSales = stockUpSales.add(forecastSalesList.get(i + 1 + turnoverDays));
            }
        }

        /*
         * dcAutoTurnover这个表单独给指定平台加上三天平均销量
        */
        caculatePlatformAvgSales(dcAutoTurnover);
        dcAutoTurnover.setSuppliersName(turnoverSku.getSuppliersName());
        dcAutoTurnover.setBuyerName(turnoverSku.getBuyerName());
        dcAutoTurnover.setBailunCategoryId(turnoverSku.getBailunCategoryId());
        dcAutoTurnover.setProductCode(turnoverSku.getProductCode());
        dcAutoTurnover.setProductInnerCode(turnoverSku.getProductInnerCode());
        dcAutoTurnover.setPaymentBeforeDelivery(paymentBeforeDelivery);
        dcAutoTurnover.setStockUpDays(stockUpDays);
        dcAutoTurnover.setStockUpSales(stockUpSales);
        dcAutoTurnover.setQuantityBeginAdvise(forecastPurchaseAdvisedList.get(0));
        dcAutoTurnover.setForecastTurnoverdaySales(avgForecastFourteendaySales.divide(BigDecimal.valueOf(turnoverDays), 3, RoundingMode.HALF_EVEN));
        dcAutoTurnover.setSalesExplainDetails(forecastSalesExplainList.toString());
        dcAutoTurnover.setHistorySevendaySales(avgHistorySevendaySales);
        dcAutoTurnover.setHistoryFourteendaySales(avgHistoryFourteendaySales);
        dcAutoTurnover.setHistoryThirtydaySales(avgHistoryThirtydaySales);
        dcAutoTurnover.setSupplementarySales(forecastSalesList.get(turnoverDays));
        dcAutoTurnover.setAverageSupplierDelivery(dcAutoTurnover.getSupplierDelivery());
        dcAutoTurnover.setSalesUpperLimit(salesUpperLimit);
        dcAutoTurnover.setAverageOverdue(BigDecimal.ZERO);
        dcAutoTurnover.setAdvisedDetails(moqPurchaseAdvisedList.toString());
        dcAutoTurnover.setSalesDetails(forecastSalesList.toString());
        dcAutoTurnover.setInboundDetails(forecastInboundList.toString());
        dcAutoTurnover.setInventoryDetails(forecastInventoryList.toString());
        dcAutoTurnover.setShortSupplyDetails(forecastShortSupplyList.toString());
        dcAutoTurnover.setForecastOnedaySales(avgForecastOnedaySales.compareTo(BigDecimal.ZERO) == 1 ? avgForecastOnedaySales.setScale(3, RoundingMode.HALF_EVEN) : BigDecimal.ZERO);
        dcAutoTurnover.setForecastSevendaySales(avgForecastSevendaySales.compareTo(BigDecimal.ZERO) == 1 ? avgForecastSevendaySales.divide(Constant.BIGDECIMAL_SEVEN, 3, RoundingMode.HALF_EVEN) : BigDecimal.ZERO);
        dcAutoTurnover.setForecastFourteendaySales(avgForecastFourteendaySales.compareTo(BigDecimal.ZERO) == 1 ? avgForecastFourteendaySales.divide(Constant.BIGDECIMAL_FOURTEEN, 3, RoundingMode.HALF_EVEN) : BigDecimal.ZERO);
        dcAutoTurnover.setQuantityInbound(quantityInbound);
        dcAutoTurnover.setInventoryAvailableDays(inventoryAvailableDays);
        dcAutoTurnover.setSkuTitle(turnoverSku.getSkuTitleCn());
        dcAutoTurnover.setPromotions(promotionsBuilder.toString());
        dcAutoTurnover.setQuantityInitAdvise(forecastPurchaseAdvisedList.get(0).add(stockUpSales));
        //如果初始建议数 + 备货数 > moq建议数, 那么moq建议数 + 备货数, 否则, 还是取moq建议数
        BigDecimal subtract = dcAutoTurnover.getQuantityInitAdvise().subtract(moqPurchaseAdvisedList.get(0));
        dcAutoTurnover.setQuantityFinalAdvise(moqPurchaseAdvisedList.get(0));
        if (subtract.compareTo(BigDecimal.ZERO) == 1) {
            dcAutoTurnover.setQuantityFinalAdvise(moqPurchaseAdvisedList.get(0).add(subtract));
        }
        dcAutoTurnover.setQuantityMinimumOrder(turnoverSku.getMoq());
        dcAutoTurnover.setQuantityInventory(realInventory);
        dcAutoTurnover.setQuantityPurchase(purchase);
        dcAutoTurnover.setQuantityTransfer(transfer);
        dcAutoTurnover.setQuantityOutStock(outStock);
        dcAutoTurnover.setStatus(forecastShortSupplyList.get(2 * turnoverDays.intValue()).intValue() > 0 ? 1 : 0);
        dcAutoTurnover.setTurnoverDays(turnoverDays);
        dcAutoTurnover.setQuantitySafeInventory(totalSafeInventory);

        dcBaseStock.setSkuTitleCn(turnoverSku.getSkuTitleCn());
        dcBaseStock.setProductInnerCode(turnoverSku.getProductInnerCode());
        dcBaseStock.setSuppliersName(turnoverSku.getSuppliersName());
        try {
            DcAutoTurnoverMapper autoTurnoverMapper = SessionUtil.getSession().getMapper(DcAutoTurnoverMapper.class);
            DcBaseStockMapper dcBaseStockMapper = SessionUtil.getSession().getMapper(DcBaseStockMapper.class);
            int i = autoTurnoverMapper.updateByExampleSelective(dcAutoTurnover, DcAutoTurnoverExample.newAndCreateCriteria().andBailunSkuEqualTo(dcAutoTurnover.getBailunSku()).andWarehouseCodeEqualTo(dcAutoTurnover.getWarehouseCode()).example());
            if (i == 0) {
                autoTurnoverMapper.insertSelective(dcAutoTurnover);
            }
            dcBaseStockMapper.updateByExampleSelective(dcBaseStock, DcBaseStockExample.newAndCreateCriteria().andBailunSkuEqualTo(dcAutoTurnover.getBailunSku()).andWarehouseCodeEqualTo(dcAutoTurnover.getWarehouseCode()).example());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB失败", e);
        } finally {
            SessionUtil.closeSession();
        }
    }

    private void caculatePlatformAvgSales(DcAutoTurnover dcAutoTurnover) {
        try {
            DcAutoSalesMapper mapper = SessionUtil.getSession().getMapper(DcAutoSalesMapper.class);
            SalesVolumeAvgDTO ebayAvgSales = mapper.getPlatformHistorySales(dcAutoTurnover.getBailunSku(), dcAutoTurnover.getWarehouseCode(),PlatformType.Ebay.value());
            dcAutoTurnover.setHistorySevendaySalesEbay(BigDecimal.ZERO);
            dcAutoTurnover.setHistoryFourteendaySalesEbay(BigDecimal.ZERO);
            dcAutoTurnover.setHistoryThirtydaySalesEbay(BigDecimal.ZERO);
            if (ebayAvgSales != null) {
                dcAutoTurnover.setHistorySevendaySalesEbay(BigDecimal.valueOf(ebayAvgSales.getSevendaySales()).divide(Constant.BIGDECIMAL_SEVEN, 3, RoundingMode.HALF_EVEN));
                dcAutoTurnover.setHistoryFourteendaySalesEbay(BigDecimal.valueOf(ebayAvgSales.getFourteendaySales()).divide(Constant.BIGDECIMAL_FOURTEEN, 3, RoundingMode.HALF_EVEN));
                dcAutoTurnover.setHistoryThirtydaySalesEbay(BigDecimal.valueOf(ebayAvgSales.getThirtydaySales()).divide(Constant.BIGDECIMAL_THIRTY, 3, RoundingMode.HALF_EVEN));
            }
        } catch (Exception e) {
           throw new RuntimeException("计算平台的平均销量失败", e);
        } finally {
            SessionUtil.closeSession();
        }

    }

    private Integer getStockUpDays(String warehouseCode) {
        DcAutoConfigStockUpDaysMapper mapper = SessionUtil.getSession().getMapper(DcAutoConfigStockUpDaysMapper.class);
        DcAutoConfigStockUpDays dcAutoConfigStockUpDays = mapper.selectOneByExample(DcAutoConfigStockUpDaysExample.newAndCreateCriteria().andWarehouseCodeEqualTo(warehouseCode).example());
        if (dcAutoConfigStockUpDays != null && dcAutoConfigStockUpDays.getStatus() == 1) {
            return dcAutoConfigStockUpDays.getStockUpDays();//备货天数减去1, 因为我们已经考虑到了一天的预计销量.
        }
        return  0;
    }

    private DcAutoConfigSkuWarehouse getAutoConfigSkuWarehouse(String bailunSku, String warehouseCode) {
        try {
            DcAutoConfigSkuWarehouseMapper autoConfigSkuWarehouseMapper = SessionUtil.getSession().getMapper(DcAutoConfigSkuWarehouseMapper.class);
            return autoConfigSkuWarehouseMapper.selectOneByExample(DcAutoConfigSkuWarehouseExample.newAndCreateCriteria().andBailunSkuEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).example());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB失败", e);
        } finally {
            SessionUtil.closeSession();
        }
    }

    private boolean actualForecastFluctuations(DcAutoTurnover dcAutoTurnover,LocalDate recordTime, List<BigDecimal> forecastSalesList, Integer realInventory, BigDecimal totalSafeInventory, Integer outStock, DcAutoConfigSkuWarehouse dcAutoConfigSkuWarehouse, BigDecimal avgHistoryFourteendaySales, BigDecimal avgHistoryThreedaySales, List<DcAutoForecastFluctuation> forecastFluctuationList) {
        boolean hasMonitorFluctuation = false;//默认是停止了监控波动功能的
        /*
         * 这个if判断单纯是为了判断实际冗余,然后存一个表给别人用
         */
        if (realInventory > forecastSalesList.get(0).add(totalSafeInventory).doubleValue()) {
            DcAutoDailyRedundance dcAutoDailyRedundance = new DcAutoDailyRedundance();
            float v = realInventory - forecastSalesList.get(0).add(totalSafeInventory).floatValue();
            dcAutoDailyRedundance.setQuantity(Math.round(v));
            dcAutoDailyRedundance.setBailunSku(dcAutoTurnover.getBailunSku());
            dcAutoDailyRedundance.setWarehouseCode(dcAutoTurnover.getWarehouseCode());
            dcAutoDailyRedundance.setWarehouseName(dcAutoTurnover.getWarehouseName());
            dcAutoDailyRedundance.setRecordTime(LocalDate.now().minusDays(1));
            try {
                DcAutoDailyRedundanceMapper mapper = SessionUtil.getSession().getMapper(DcAutoDailyRedundanceMapper.class);
                int i = mapper.updateByExampleSelective(dcAutoDailyRedundance,DcAutoDailyRedundanceExample.newAndCreateCriteria().andBailunSkuEqualTo(dcAutoDailyRedundance.getBailunSku()).andWarehouseCodeEqualTo(dcAutoDailyRedundance.getWarehouseCode()).andRecordTimeEqualTo(dcAutoDailyRedundance.getRecordTime()).example());
                if (i == 0) {
                    mapper.insertSelective(dcAutoDailyRedundance);
                }
            } catch (Exception e) {
                throw new RuntimeException("MYBATIS操作dcAutoDailyRedundance失败",e);
            } finally {
                SessionUtil.closeSession();
            }
        }
        /*
         * 如果没有配置停止监控
         * 库存大于0或者平均销量大于0
         * 那就要监控波动
         */
        if ((dcAutoConfigSkuWarehouse == null && (realInventory > 0 || avgHistoryFourteendaySales.compareTo(BigDecimal.ZERO) == 1)) || (dcAutoConfigSkuWarehouse != null && dcAutoConfigSkuWarehouse.getStatus() == 0 && (realInventory > 0 || avgHistoryFourteendaySales.compareTo(BigDecimal.ZERO) == 1))) {
            hasMonitorFluctuation = true;
            DcAutoForecastFluctuation dcAutoForecastFluctuationActual = null;
            if (realInventory > forecastSalesList.get(0).add(totalSafeInventory).doubleValue()) {
                dcAutoForecastFluctuationActual = new DcAutoForecastFluctuation();
                //实际冗余
                dcAutoForecastFluctuationActual.setType(3);
                float v = realInventory - forecastSalesList.get(0).add(totalSafeInventory).floatValue();
                dcAutoForecastFluctuationActual.setQuantity(Math.round(v));
            }

            if (outStock > 0) {
                dcAutoForecastFluctuationActual = new DcAutoForecastFluctuation();
                //实际缺货
                dcAutoForecastFluctuationActual.setType(1);
                dcAutoForecastFluctuationActual.setQuantity(outStock);
            }
            if (dcAutoForecastFluctuationActual != null) {
                dcAutoForecastFluctuationActual.setAvgSalesThree(avgHistoryThreedaySales);
                dcAutoForecastFluctuationActual.setStartTime(recordTime);
                dcAutoForecastFluctuationActual.setEndTime(recordTime);
                forecastFluctuationList.add(dcAutoForecastFluctuationActual);
            }
        }
        return hasMonitorFluctuation;
    }

    /**
     * 不正常的波动
     * 实际缺货的，预计缺货
     * 实际冗余的，预计冗余
     *
     * @param warehouseCode
     * @param dcAutoTurnover
     * @param forecastFluctuationList
     */
    private void unusualFluctuation(DcAutoConfigSkuWarehouse dcAutoConfigSkuWarehouse, DcBaseSku turnoverSku, String warehouseCode, DcAutoTurnover dcAutoTurnover, BigDecimal avgHistoryThreedaySales, List<DcAutoForecastFluctuation> forecastFluctuationList) {
        //status`  '监控状态, 默认0监控, 停止监控1',
        try {
            DcAutoForecastFluctuationMapper mapper = SessionUtil.getSession().getMapper(DcAutoForecastFluctuationMapper.class);
            //JIT的 可以不做 冗余 和 缺货预测 实际冗余和实际缺货也不用提醒
            //如果仓库是广州01 且 是这三个销售员, 就不提醒
            if (!warehouseCode.equals(Constant.WAREHOUSE_JIT) || !turnoverSku.getBuyerName().equals(Constant.BUYER_JIT_1) && !turnoverSku.getBuyerName().equals(Constant.BUYER_JIT_2) && !turnoverSku.getBuyerName().equals(Constant.BUYER_JIT_3)) {
                if (dcAutoConfigSkuWarehouse == null || dcAutoConfigSkuWarehouse.getStatus().equals(0)) {
                    if (forecastFluctuationList != null && forecastFluctuationList.size() > 0) {
                        //拿最早的时间  +  最晚的时间, SKU-仓库维度只放一条
                        DcAutoForecastFluctuation dcAutoForecastFluctuation = forecastFluctuationList.get(0);
                        dcAutoForecastFluctuation.setEndTime(forecastFluctuationList.get(forecastFluctuationList.size() - 1).getEndTime());
                        dcAutoForecastFluctuation.setBailunSku(turnoverSku.getBailunSku());
                        dcAutoForecastFluctuation.setWarehouseCode(warehouseCode);
                        dcAutoForecastFluctuation.setWarehouseName(dcAutoTurnover.getWarehouseName());
                        dcAutoForecastFluctuation.setAvgSalesThree(avgHistoryThreedaySales);
                        DcAutoForecastFluctuation dcAutoForecastFluctuationSelected = mapper.selectOneByExample(DcAutoForecastFluctuationExample.newAndCreateCriteria().andBailunSkuEqualTo(turnoverSku.getBailunSku()).andWarehouseCodeEqualTo(warehouseCode).example());
                        if (dcAutoForecastFluctuationSelected != null) {
                            dcAutoForecastFluctuation.setStartTime(dcAutoForecastFluctuationSelected.getStartTime());
                            mapper.updateByExampleSelective(dcAutoForecastFluctuation, DcAutoForecastFluctuationExample.newAndCreateCriteria().andBailunSkuEqualTo(dcAutoForecastFluctuation.getBailunSku()).andWarehouseCodeEqualTo(dcAutoForecastFluctuation.getWarehouseCode()).example());
                        } else {
                            mapper.insertSelective(dcAutoForecastFluctuation);
                        }
                    }
                } else if (dcAutoConfigSkuWarehouse != null && dcAutoConfigSkuWarehouse.getStatus().equals(1)) {
                    mapper.deleteByExample(DcAutoForecastFluctuationExample.newAndCreateCriteria().andBailunSkuEqualTo(turnoverSku.getBailunSku()).andWarehouseCodeEqualTo(warehouseCode).example());
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("MYBATIS操作DB存DcAutoForecastFluctuation失败", e);
        } finally {
            SessionUtil.closeSession();
        }
    }

    private Integer getTurnoverDelivery(DcBaseSku turnoverSku, DcBaseWarehouse dcBaseWarehouse,DcAutoTurnover dcAutoTurnover, DcAutoConfigDelivery dcAutoConfigDelivery, DcAveragePurchase dcAveragePurchase, DcAverageWarehouse dcAverageWarehouse) {
        /*
         * 供应商交期
         * 先取dc_average_purchase,如果平均交期没有
         * 再去SKUMS取, 取SKUMS数据的时候如果是国内仓，就还取dc_base_sku原来那个字段，否则就要取dc_base_sku原新字段（调拨交期)
         */
        Integer turnoverSupplierDelivery = dcAveragePurchase != null && dcAveragePurchase.getDeliveryDays() >= 0? dcAveragePurchase.getDeliveryDays() : -1;
        if (turnoverSupplierDelivery == null || turnoverSupplierDelivery.equals(-1)) {
            turnoverSupplierDelivery = turnoverSku.getSupplierDelivery();
            //如果是国内仓，就还取原来那个字段，否则就要取新字段（调拨交期)
            if (dcBaseWarehouse != null && !dcBaseWarehouse.getHqType().equals(CommonConstant.DOMESTIC_WAREHOUSE)) {
                turnoverSupplierDelivery = turnoverSku.getTransferDelivery();
            }
        }
        //入库天数
        Integer turnoverInboundDelivery = Constant.INSPECTION_DELIVE;
        //质检入库天数-配置值(实际还没用上)
        dcAutoTurnover.setInspectionConfigDelivery(turnoverInboundDelivery);
        //调拨头程
        Integer turnoverTransferHeadDelivery = dcAutoConfigDelivery.getTranferHead();
        dcAutoTurnover.setTransferConfigDelivery(turnoverTransferHeadDelivery);
        //调拨打包
        Integer turnoverTransferBaleDelivery = dcAutoConfigDelivery.getTranferBale();
        dcAutoTurnover.setTransferBaleConfigDelivery(turnoverTransferBaleDelivery);
        //海外仓入库
        Integer turnoverAbroadInboundDelivery = dcAutoConfigDelivery.getAbroadInbound();
        dcAutoTurnover.setAbroadInboundConfigDelivery(turnoverAbroadInboundDelivery);


        if (dcAverageWarehouse != null) {
            if (dcAverageWarehouse.getInboundDays() != null && dcAverageWarehouse.getInboundDays() > 0) {
                turnoverInboundDelivery = dcAverageWarehouse.getInboundDays();
            }
            if (dcAverageWarehouse.getTransferDelivery() != null && dcAverageWarehouse.getTransferDelivery().compareTo(BigDecimal.ZERO) > 0) {
                turnoverTransferHeadDelivery = dcAverageWarehouse.getTransferDelivery().intValue();
            }
            if (dcAverageWarehouse.getTransferBaleDelivery() != null && dcAverageWarehouse.getTransferBaleDelivery().compareTo(BigDecimal.ZERO) > 0) {
                turnoverTransferBaleDelivery = dcAverageWarehouse.getTransferBaleDelivery().intValue();
            }
            if (dcAverageWarehouse.getAbroadInboundDelivery() != null && dcAverageWarehouse.getAbroadInboundDelivery().compareTo(BigDecimal.ZERO) > 0) {
                turnoverAbroadInboundDelivery = dcAverageWarehouse.getAbroadInboundDelivery().intValue();
            }
        }

        //周转期
        Integer turnoverDays = turnoverSupplierDelivery + turnoverInboundDelivery + turnoverTransferHeadDelivery + turnoverTransferBaleDelivery + turnoverAbroadInboundDelivery;
        dcAutoTurnover.setSupplierDelivery(turnoverSupplierDelivery);
        //质检天数
        dcAutoTurnover.setInspectionDelivery(turnoverInboundDelivery);
        dcAutoTurnover.setTransferDelivery(turnoverTransferHeadDelivery);
        dcAutoTurnover.setTransferBaleDelivery(turnoverTransferBaleDelivery);
        dcAutoTurnover.setAbroadInboundDelivery(turnoverAbroadInboundDelivery);
        return turnoverDays;
    }


    /**
     * 获取百伦一级分类Id
     *
     * @param turnoverSku
     * @return
     * @throws Exception
     */
    private Integer getBailunCatagoryId(DcBaseSku turnoverSku) throws Exception {
        Integer bailunFirstLevelCatagoryId = null;
        String parentCategories = null;
        if (turnoverSku != null && turnoverSku.getBailunCategoryId() != null) {
            parentCategories = skuMSRequest(turnoverSku.getBailunCategoryId());
            List<CatagoryDTO> catagoryDTOS = JSON.parseObject(parentCategories, new TypeReference<List<CatagoryDTO>>() {
            });
            if (catagoryDTOS != null && catagoryDTOS.size() > 0) {
                for (CatagoryDTO catagoryDTO : catagoryDTOS) {
                    if (catagoryDTO.getC_level() == 1) {
                        bailunFirstLevelCatagoryId = catagoryDTO.getC_id();
                    }
                }
            }
        }
        return bailunFirstLevelCatagoryId;
    }

    private BigDecimal caculateWeightingAvgSales(BigDecimal weightingCoefficientSeven, BigDecimal weightingCoefficientFourteen, BigDecimal weightingCoefficientThirty, List<BigDecimal> salesList) {
        List<BigDecimal> historySevenSalesList = new ArrayList<>(7);
        List<BigDecimal> historyFourteenSalesList = new ArrayList<>(14);
        for (int i = 16; i < salesList.size(); i++) {
            historyFourteenSalesList.add(salesList.get(i));
            if (i >= 23) {
                historySevenSalesList.add(salesList.get(i));
            }
        }
        //自定义Comparator对象，自定义排序
        Comparator c = new Comparator<BigDecimal>() {
            @Override
            public int compare(BigDecimal o1, BigDecimal o2) {
                if (o1.compareTo(o2) == -1)
                    return 1;
                    //注意！！返回值必须是一对相反数，否则无效。jdk1.7以后就是这样。
                else return -1;
            }
        };
        salesList.sort(c);
        historyFourteenSalesList.sort(c);
        historySevenSalesList.sort(c);
        BigDecimal thirtyHistorySales = BigDecimal.ZERO;
        BigDecimal fourteenHistorySales = BigDecimal.ZERO;
        BigDecimal sevenHistorySales = BigDecimal.ZERO;
        for (int i = 1; i < salesList.size() - 1; i++) {
            thirtyHistorySales = thirtyHistorySales.add(salesList.get(i));
        }
        for (int i = 1; i < historyFourteenSalesList.size() - 1; i++) {
            fourteenHistorySales = fourteenHistorySales.add(historyFourteenSalesList.get(i));
        }
        for (int i = 1; i < historySevenSalesList.size() - 1; i++) {
            sevenHistorySales = sevenHistorySales.add(historySevenSalesList.get(i));
        }
        BigDecimal weightingSevenAvgSales = thirtyHistorySales.divide(BigDecimal.valueOf(salesList.size() - 2), 3, RoundingMode.HALF_EVEN);
        BigDecimal weightingFourteenAvgSales = fourteenHistorySales.divide(BigDecimal.valueOf(historyFourteenSalesList.size() - 2), 3, RoundingMode.HALF_EVEN);
        BigDecimal weightingThirtyAvgSales = sevenHistorySales.divide(BigDecimal.valueOf(historySevenSalesList.size() - 2), 3, RoundingMode.HALF_EVEN);
        BigDecimal weightingAvgSales = weightingSevenAvgSales.multiply(weightingCoefficientSeven).add(weightingFourteenAvgSales.multiply(weightingCoefficientFourteen)).add(weightingThirtyAvgSales.multiply(weightingCoefficientThirty)).setScale(3, RoundingMode.HALF_EVEN);
        return weightingAvgSales;
    }

    /**
     * 预计入库
     */
    private void ExpectedStorage(List<String> forecastInboundRelationList, Integer purchase, Integer transfer, LocalDate recordTime, DcAutoTurnover dcAutoTurnover, List<Integer> forecastPurchaseInboundList, List<Integer> forecastTransferInboundList, List<Integer> forecastInboundList) {
        /*
         * 拿到预计到货时间在周转期内的采购单
         * 采购系统的预计到货只是到货时间
         * 还需要加上入库时长,
         * 且不同交通方式的入库时长不一致
         */
        //拿采购单流水
        if (purchase > 0) {
            List<DcBasePurchase> dcBasePurchaseList = null;
            // 找采购在途的采购单
            try {
                DcBasePurchaseMapper dcBasePurchaseMapper = SessionUtil.getSession().getMapper(DcBasePurchaseMapper.class);
                dcBasePurchaseList = dcBasePurchaseMapper.listPurchaseTransitOrder(dcAutoTurnover.getBailunSku(), dcAutoTurnover.getWarehouseCode());
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Mybatis操作DB查询采购单流水失败");
            } finally {
                SessionUtil.closeSession();
            }
            if (dcBasePurchaseList != null && dcBasePurchaseList.size() > 0) {
                for (DcBasePurchase dcBasePurchase : dcBasePurchaseList) {
                    if (dcBasePurchase.getCount() > 0) {
                        LocalDateTime finalEstimatedArrivalTime = null;
                        if (dcBasePurchase.getHasTransfer() == 1) { //调拨采购单
                            finalEstimatedArrivalTime = dcBasePurchase.getEstimatedArrivalTime().plusDays(dcAutoTurnover.getInspectionDelivery()).plusDays(dcAutoTurnover.getTransferBaleDelivery()).plusDays(dcAutoTurnover.getTransferDelivery()).plusDays(dcAutoTurnover.getAbroadInboundDelivery());
                        } else {//普通采购单
                            finalEstimatedArrivalTime = dcBasePurchase.getEstimatedArrivalTime().plusDays(dcAutoTurnover.getInspectionDelivery());
                        }
                        int interval = Period.between(recordTime, finalEstimatedArrivalTime.toLocalDate()).getDays();
                        int index = interval <= 0 ? 0 : interval;
                        forecastInboundRelationList.set(index, forecastInboundRelationList.get(index).equals(Constant.NAN_STRING) ? Constant.PURCHASE_SIGN + dcBasePurchase.getPurchaseId() + "_" + dcBasePurchase.getCount()  : forecastInboundRelationList.get(index) + "*" + Constant.PURCHASE_SIGN + dcBasePurchase.getPurchaseId() + "_" + dcBasePurchase.getCount());
                        forecastPurchaseInboundList.set(index, forecastPurchaseInboundList.get(index) + dcBasePurchase.getCount());
                        forecastInboundList.set(index, forecastPurchaseInboundList.get(index) + forecastTransferInboundList.get(index));
                    }
                }
            }
        }
        //拿调拨单流水
        if (transfer > 0) {
            List<DcBaseTransferVerify> dcBaseTransferVerifyList = null;
            try {
                DcBaseTransferVerifyMapper dcBaseTransferVerifyMapper = SessionUtil.getSession().getMapper(DcBaseTransferVerifyMapper.class);
                // 找调拨在途的采购单
                dcBaseTransferVerifyList = dcBaseTransferVerifyMapper.listTransferTransitOrder(dcAutoTurnover.getBailunSku(), dcAutoTurnover.getWarehouseCode());
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Mybatis操作DB查询调拨单流水失败");
            } finally {
                SessionUtil.closeSession();
            }
            if (dcBaseTransferVerifyList != null && dcBaseTransferVerifyList.size() > 0) {
                for (DcBaseTransferVerify dcBaseTransferVerify : dcBaseTransferVerifyList) {
                    if (dcBaseTransferVerify.getCount() > 0) {
                        LocalDateTime finalEstimatedArrivalTime = null;
                        if (dcBaseTransferVerify.getEstimatedArrivalTime() != null && dcBaseTransferVerify.getEstimatedArrivalTime().isAfter(Constant.INIT_DATE_TIME)) {
                            finalEstimatedArrivalTime = dcBaseTransferVerify.getEstimatedArrivalTime().plusDays(dcAutoTurnover.getInspectionConfigDelivery());
                        } else {
                            finalEstimatedArrivalTime = dcBaseTransferVerify.getCreateTime().plusDays(dcAutoTurnover.getInspectionConfigDelivery()).plusDays(dcAutoTurnover.getTransferBaleDelivery()).plusDays(dcAutoTurnover.getTransferDelivery()).plusDays(dcAutoTurnover.getAbroadInboundDelivery());
                        }
                        int interval = Period.between(recordTime, finalEstimatedArrivalTime.toLocalDate()).getDays();
                        int index = interval <= 0 ? 0 : interval;
                        forecastInboundRelationList.set(index, forecastInboundRelationList.get(index).equals(Constant.NAN_STRING) ? Constant.TRANSFER_SIGN + dcBaseTransferVerify.getTransferOrderId() + "_" + dcBaseTransferVerify.getCount() : forecastInboundRelationList.get(index) + "*" + Constant.TRANSFER_SIGN + dcBaseTransferVerify.getTransferOrderId() + "_" + dcBaseTransferVerify.getCount());
                        forecastTransferInboundList.set(index, forecastTransferInboundList.get(index) + dcBaseTransferVerify.getCount());
                        forecastInboundList.set(index, forecastPurchaseInboundList.get(index) + forecastTransferInboundList.get(index));
                    }
                }
            }
        }
    }

    private StringBuilder promotionForecastSales(LocalDate recordTime, Integer autoForecastDay, DcAutoSales
            dcAutoSales, List<BigDecimal> forecastSalesList) {
        //周转周期里有活动,可能有多个
        Integer promotionCount = 0;
        StringBuilder promotionsBuilder = new StringBuilder();
        List<DcAutoConfigPromotion> autoSalesConfigList = null;
        try {
            DcAutoConfigPromotionMapper dcAutoConfigPromotionMapper = SessionUtil.getSession().getMapper(DcAutoConfigPromotionMapper.class);
            autoSalesConfigList = dcAutoConfigPromotionMapper.selectByExample(DcAutoConfigPromotionExample.newAndCreateCriteria().andBailunSkuEqualTo(dcAutoSales.getBailunSku()).andWarehouseCodeEqualTo(dcAutoSales.getWarehouseCode()).andPromotionTimeBetween(recordTime, recordTime.plusDays(autoForecastDay.longValue())).example());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB查询周转周期里活动失败");
        } finally {
            SessionUtil.closeSession();
        }
        if (autoSalesConfigList != null && autoSalesConfigList.size() > 0) {
            for (int j = 0; j < autoSalesConfigList.size(); j++) {
                DcAutoConfigPromotion dcAutoConfigPromotion = autoSalesConfigList.get(j);
                if (promotionsBuilder.length() > 0) {
                    promotionsBuilder.append(",");
                }
                promotionsBuilder.append(dcAutoConfigPromotion.getId());
                //看促销日期和recordTime相差多少天
                int interval = Period.between(recordTime, dcAutoConfigPromotion.getPromotionTime()).getDays();
                if (interval >= 0 && interval < autoForecastDay) {
                    promotionCount = promotionCount + dcAutoConfigPromotion.getCount().intValue();
                    forecastSalesList.set(interval, forecastSalesList.get(interval).add(BigDecimal.valueOf(dcAutoConfigPromotion.getCount())));
                }
            }
        }
        return promotionsBuilder;
    }

    /**
     * 销量表只有31天的预测销量, 2倍周转期超过这个时间的不满足要求
     * 只能根据公式算好放list里
     * x 的取值从31开始
     *
     * @param salesUpperLimit   预测销量上限
     * @param autoForecastDay
     * @param dcAutoSales
     * @param forecastSalesList
     */
    private void calculateForecastSales(BigDecimal salesUpperLimit, Integer autoForecastDay, DcAutoSales
            dcAutoSales, List<BigDecimal> forecastSalesList) {
        // y = ae^bx
        for (int j = 0; j < autoForecastDay; j++) {
            double v;
            if (dcAutoSales.getFitForecastFormula().contains("ln(x)")) {
                //y = aln(x) + b
                v = dcAutoSales.getFitAVariable().doubleValue() * Math.log1p((j + 31)) + dcAutoSales.getFitBVariable().doubleValue();
            } else {
                v = dcAutoSales.getFitAVariable().doubleValue() * Math.exp(dcAutoSales.getFitBVariable().doubleValue() * (j + 31));
            }
            BigDecimal forecastSales = BigDecimal.valueOf(v < 0 ? 0 : v).setScale(0, RoundingMode.HALF_EVEN);
            forecastSalesList.set(j, forecastSales.compareTo(salesUpperLimit) == 1 ? salesUpperLimit : forecastSales);
        }
    }

    /**
     * 去数据库查出矫正曲线配置
     * 用配置的参数乘以指定销量
     * dcAutoConfigCorrection.getStartTime() 要大于等于queryTime
     *
     * @param autoForecastDay
     * @param dcAutoSales
     * @param forecastSalesList
     */
    private void correctionForecastSales(LocalDate recordTime, Integer autoForecastDay, DcAutoSales
            dcAutoSales, List<BigDecimal> forecastSalesList) {
        List<DcAutoConfigCorrection> configCorrectionList = null;
        try {
            DcAutoConfigCorrectionMapper autoConfigCorrectionMapper = SessionUtil.getSession().getMapper(DcAutoConfigCorrectionMapper.class);
            configCorrectionList = autoConfigCorrectionMapper.selectByExample(DcAutoConfigCorrectionExample.newAndCreateCriteria().andBailunSkuEqualTo(dcAutoSales.getBailunSku()).andWarehouseCodeEqualTo(dcAutoSales.getWarehouseCode()).andStartTimeGreaterThanOrEqualTo(recordTime).example());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB查询矫正曲线配置失败");
        } finally {
            SessionUtil.closeSession();
        }
        if (configCorrectionList != null && configCorrectionList.size() > 0) {
            for (DcAutoConfigCorrection dcAutoConfigCorrection : configCorrectionList) {
                if (dcAutoConfigCorrection.getStartTime() != null && dcAutoConfigCorrection.getEndTime() == null) {
                    int interval = Period.between(recordTime, dcAutoConfigCorrection.getStartTime()).getDays();
                    BigDecimal param = dcAutoConfigCorrection.getParam();
                    forecastSalesList.set(interval, param.multiply(forecastSalesList.get(interval)).setScale(3, RoundingMode.HALF_EVEN));
                } else if (dcAutoConfigCorrection.getStartTime() != null && dcAutoConfigCorrection.getEndTime() != null) {
                    int begin = Period.between(recordTime, dcAutoConfigCorrection.getStartTime()).getDays();
                    int end = Period.between(recordTime, dcAutoConfigCorrection.getEndTime()).getDays();
                    end = (end > autoForecastDay.intValue()) ? autoForecastDay.intValue() : end;
                    for (int i = begin; i <= end; i++) {
                        forecastSalesList.set(i, dcAutoConfigCorrection.getParam().multiply(forecastSalesList.get(i)).setScale(3, RoundingMode.HALF_EVEN));
                    }
                }
            }
        }
    }

    /**
     * 初始化容器, 存放预测数据
     *
     * @param forecastInventoryList       预测库存
     * @param forecastPurchaseInboundList 预测采购入库
     * @param forecastTransferInboundList 预测调拨入库
     * @param forecastInboundList         预测入库总数
     * @param forecastSalesList           预测销量
     * @param forecastShortSupplyList     预测断货
     * @param autoForecastDay             周转倍数
     */
    private void initForecaseList
    (List<String> forecastInboundRelationList, List<BigDecimal> forecastInventoryList, List<Integer> forecastPurchaseInboundList, List<Integer> forecastTransferInboundList, List<Integer> forecastInboundList, List<BigDecimal> forecastSalesList, List<BigDecimal> forecastShortSupplyList, Integer
            autoForecastDay, List<String> forecastSalesExplainList) {
        for (int i1 = 0; i1 < autoForecastDay; i1++) {
            forecastInboundList.add(0);
            forecastInventoryList.add(BigDecimal.ZERO);
            forecastSalesList.add(BigDecimal.ZERO);
            forecastSalesExplainList.add(Constant.ALGORITHM_NAME);
            forecastShortSupplyList.add(BigDecimal.ZERO);
            forecastPurchaseInboundList.add(0);
            forecastTransferInboundList.add(0);
            forecastTransferInboundList.add(0);
            forecastInboundRelationList.add(Constant.NAN_STRING);
        }
    }


    private BigDecimal calculateSalesUpperLimit(String bailunSku, String warehouseCode, Integer
            bailunFirstLevelCatagoryId, DcAutoSales dcAutoSales, String forecastSalesListJson) throws Exception {
        /*
         * 1. 根据仓库SKU找销量上限, 有就停止, 没有则2
         * 2. 根据仓库和分类找销量上限, 有就停止, 没有则3
         * 3. 根据加权平均算上限 * 倍数
         */
        DcAutoConfigSalesUpperLimitMapper salesUpperLimitMapper = null;
        DcAutoConfigSalesUpperLimit dcAutoConfigSalesUpperLimit = null;
        try {
            salesUpperLimitMapper = SessionUtil.getSession().getMapper(DcAutoConfigSalesUpperLimitMapper.class);
            dcAutoConfigSalesUpperLimit = salesUpperLimitMapper.selectOneByExample(DcAutoConfigSalesUpperLimitExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(1).example());
            if (dcAutoConfigSalesUpperLimit == null) {
                if (bailunFirstLevelCatagoryId != null) {//部分SKU没有百伦分类Id
                    dcAutoConfigSalesUpperLimit = salesUpperLimitMapper.selectOneByExample(DcAutoConfigSalesUpperLimitExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunFirstLevelCatagoryId.toString()).andWarehouseCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(2).example());
                }
            }
            if (dcAutoConfigSalesUpperLimit == null) {
                BigDecimal weightingAvgSales;
                //历史
                dcAutoConfigSalesUpperLimit = salesUpperLimitMapper.selectOneByExample(DcAutoConfigSalesUpperLimitExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(1).example());
                List<BigDecimal> historyThirtySalesList = JSON.parseObject(dcAutoSales.getHistorySalesDetails(), new TypeReference<List<BigDecimal>>() {
                });
                if (dcAutoConfigSalesUpperLimit != null) {
                    weightingAvgSales = caculateWeightingAvgSales(dcAutoConfigSalesUpperLimit.getWeightingCoefficientSeven(), dcAutoConfigSalesUpperLimit.getWeightingCoefficientFourteen(), dcAutoConfigSalesUpperLimit.getWeightingCoefficientThirty(), historyThirtySalesList);
                    weightingAvgSales = weightingAvgSales.multiply(dcAutoConfigSalesUpperLimit.getParam()).setScale(3, RoundingMode.HALF_EVEN);
                } else {
                    //如果没有任何配置, 就留一个默认配置
                    weightingAvgSales = caculateWeightingAvgSales(BigDecimal.valueOf(0.3), BigDecimal.valueOf(0.3), BigDecimal.valueOf(0.4), historyThirtySalesList);
                }
                return weightingAvgSales;
            } else {
                return dcAutoConfigSalesUpperLimit.getParam();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB失败", e);
        } finally {
            SessionUtil.closeSession();
        }
    }

    private BigDecimal calculateSafeInventory(String bailunSku, String warehouseCode, Integer
            bailunFirstLevelCatagoryId, DcAutoSales dcAutoSales, String forecastSalesListJson,DcAutoTurnover dcAutoTurnover)  {
        /*
         * 手动设置
         * 1. 根据仓库 + SKU在表里找dc_auto_config_safe_inventory
         * 2. 根据仓库 + 类目在表里找dc_auto_config_safe_inventory
         * 当手动设置的值不为null, 就返回;
         * 当手动设置的值为null, 就自动计算加权平均销量
         *
         * 自动计算
         * 计算7天平均销量14天平均销量30天平均销量
         * 1. 先找按预测计算是否存在表
         *   a. 有就取出权重
         *   b. 没有就找按历史计算是否存在表
         * 2. 找按历史计算是否存在表
         *   a. 有就取出权重
         *   b. 没有就将7天平均销量14天平均销量30天平均销量再平均
         *
         * 最终
         * 平均交期 - 供应商交期 > 0  就是平均逾期天数
         * 平均逾期天数 * 加权平均销量(去最大值最小值)
         *
         * 新增一种算法（优先级别最低的），直接配置仓库。根据历史销量加权计算。最后可以乘以一个倍数
         */
        DcAutoConfigSafeInventoryMapper autoConfigSafeInventoryMapper = null;
        DcAutoConfigSafeInventory dcAutoConfigSafeInventory = null;
        try {
            autoConfigSafeInventoryMapper = SessionUtil.getSession().getMapper(DcAutoConfigSafeInventoryMapper.class);
            dcAutoConfigSafeInventory = autoConfigSafeInventoryMapper.selectOneByExample(DcAutoConfigSafeInventoryExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(1).example());

            if (dcAutoConfigSafeInventory == null) {
                if (bailunFirstLevelCatagoryId != null) {//部分SKU没有百伦分类Id
                    dcAutoConfigSafeInventory = autoConfigSafeInventoryMapper.selectOneByExample(DcAutoConfigSafeInventoryExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunFirstLevelCatagoryId.toString()).andWarehouseCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(2).example());
                }
            }
            if (dcAutoConfigSafeInventory == null) {
                BigDecimal weightingAvgSales = null;
                //预测
                dcAutoConfigSafeInventory = autoConfigSafeInventoryMapper.selectOneByExample(DcAutoConfigSafeInventoryExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(12).example());
                if (dcAutoConfigSafeInventory != null) {
                    List<BigDecimal> forecastSalesList = JSON.parseObject(forecastSalesListJson, new TypeReference<List<BigDecimal>>() {
                    });
                    forecastSalesList.remove(forecastSalesList.size() - 1);
                    weightingAvgSales = caculateWeightingAvgSales(dcAutoConfigSafeInventory.getWeightingCoefficientSeven(), dcAutoConfigSafeInventory.getWeightingCoefficientFourteen(), dcAutoConfigSafeInventory.getWeightingCoefficientThirty(), forecastSalesList);
                } else {
                    //历史
                    dcAutoConfigSafeInventory = autoConfigSafeInventoryMapper.selectOneByExample(DcAutoConfigSafeInventoryExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(11).example());
                    List<BigDecimal> historyThirtySalesList = JSON.parseObject(dcAutoSales.getHistorySalesDetails(), new TypeReference<List<BigDecimal>>() {
                    });
                    if (dcAutoConfigSafeInventory != null) {
                        weightingAvgSales = caculateWeightingAvgSales(dcAutoConfigSafeInventory.getWeightingCoefficientSeven(), dcAutoConfigSafeInventory.getWeightingCoefficientFourteen(), dcAutoConfigSafeInventory.getWeightingCoefficientThirty(), historyThirtySalesList);
                    } else {
                        //仓库。根据历史销量加权计算。最后可以乘以一个倍数
                        dcAutoConfigSafeInventory = autoConfigSafeInventoryMapper.selectOneByExample(DcAutoConfigSafeInventoryExample.newAndCreateCriteria().andVariableCodeEqualTo("").andWarehouseCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(3).example());
                        if (dcAutoConfigSafeInventory != null) {
                            weightingAvgSales = caculateWeightingAvgSales(dcAutoConfigSafeInventory.getWeightingCoefficientSeven(), dcAutoConfigSafeInventory.getWeightingCoefficientFourteen(), dcAutoConfigSafeInventory.getWeightingCoefficientThirty(), historyThirtySalesList);
                            //进来这里就存一份没乘倍数的加权日均销量
                            dcAutoTurnover.setDailyWeightedSales(weightingAvgSales);
                            if (weightingAvgSales.compareTo(BigDecimal.ONE) == 1) {
                                weightingAvgSales = weightingAvgSales.multiply(dcAutoConfigSafeInventory.getParam()).setScale(3, RoundingMode.HALF_EVEN);
                            } else {
                                weightingAvgSales = BigDecimal.ONE;
                            }
                        } else {
                            //啥配置都没有就给个默认取值
                            weightingAvgSales = caculateWeightingAvgSales(BigDecimal.valueOf(0.3), BigDecimal.valueOf(0.3), BigDecimal.valueOf(0.4), historyThirtySalesList);
                            dcAutoTurnover.setDailyWeightedSales(weightingAvgSales);
                        }
                    }
                }
                return weightingAvgSales;
            } else {
                return dcAutoConfigSafeInventory.getParam();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB失败", e);
        } finally {
            SessionUtil.closeSession();
        }
    }


    private Integer fbaAdvise(boolean hasMonitorFluctuation, List<DcAutoForecastFluctuation> forecastFluctuationList, LocalDate
            recordTime, DcAutoTurnover dcAutoTurnover, Integer turnoverDays, Integer
                                      autoForecastDay, List<BigDecimal> forecastInventoryList, List<Integer> forecastInboundList, List<BigDecimal> forecastSalesList, List<BigDecimal> forecastShortSupplyList, List<BigDecimal> forecastPurchaseAdvisedList, List<BigDecimal> moqPurchaseAdvisedList, Integer
                                      realInventory, BigDecimal totalSafeInventory, Integer outStock, Integer inventoryAvailableDays, BigDecimal
                                      totalSales, BigDecimal totalInbound, BigDecimal
                                      moqDecimal, List<Integer> historySalesList, List<String> forecastSalesExplainList) {
        BigDecimal inboundDecimal;//入库数量
        BigDecimal forecastInventory;//预测库存
        BigDecimal shortSupply;//缺货数量
        BigDecimal addShortSupply;// 补断货的数量
        int shortSupplyDays = 0;//断货天数
        int shortSupplyIndex = -1;//断货天索引
        int forecastFluctuationDays = 0;//断货或冗余天数 + 1
        int forecastFluctuationIndex = -1;//断货或冗余索引
        for (int j = 0; j < autoForecastDay; j++) {
            inboundDecimal = BigDecimal.valueOf(forecastInboundList.get(j));
            totalInbound = totalInbound.add(inboundDecimal);
            if (j == turnoverDays) {
                dcAutoTurnover.setTurnoverInbound(totalInbound);
            }
            //计算预计库存 和 预计缺货
            if (j == 0) { //j == 0表示, 周转计算的当天
                if (outStock > 0) {
                    forecastSalesList.set(j, BigDecimal.ZERO);
                    forecastSalesExplainList.set(j, "0, 昨天真实缺货");
                    shortSupplyIndex = j;
                    //如果有缺货数, 第一天预计库存 =  预计入库 - 缺货数
                    forecastInventory = BigDecimal.valueOf((forecastInboundList.get(j).doubleValue() - outStock.doubleValue()));
                    shortSupply = forecastInventory.compareTo(BigDecimal.ZERO) == -1 ? forecastInventory.abs() : BigDecimal.ZERO;//库存补入后的 缺货数
                    BigDecimal subtract = forecastSalesList.get(j).subtract(forecastInventory.compareTo(BigDecimal.ZERO) == 1 ? forecastInventory : BigDecimal.ZERO);//今天的缺货数
                    shortSupply = subtract.add(shortSupply).compareTo(BigDecimal.ZERO) == 1 ? subtract.add(shortSupply) : BigDecimal.ZERO;
                } else {
                    //否则, 第一天预计库存 =  真实库存 + 预计入库
                    forecastInventory = BigDecimal.valueOf((forecastInboundList.get(j).doubleValue() + realInventory.doubleValue()));
                    BigDecimal subtract = forecastSalesList.get(j).subtract(forecastInventory);
                    shortSupply = subtract.compareTo(BigDecimal.ZERO) == 1 ? subtract : BigDecimal.ZERO;
                }
                forecastShortSupplyList.set(j, forecastShortSupplyList.get(j).add(shortSupply));
            } else { //j != 0表示, 周转计算之后的天数
                if (forecastShortSupplyList.get(j - 1).compareTo(BigDecimal.ZERO) == 1) {//是断货
                    forecastSalesList.set(j, BigDecimal.ZERO);
                    forecastSalesExplainList.set(j, "昨天预测缺货");
                    if (shortSupplyIndex < 0) {
                        shortSupplyIndex = j;
                    }
                    shortSupplyDays++;
                } else {
                    if (shortSupplyIndex == 0) {
                        //尽量降序找一个不为0的历史销量
                        Integer historySaleGTZero = null;
                        for (int i = historySalesList.size() - 1; i >= 0; i--) {
                            historySaleGTZero = 0;
                            if (historySalesList.get(i) > 0) {
                                historySaleGTZero = historySalesList.get(i);
                                break;
                            }
                        }
                        //按照规则, 递减或递增
                        for (int i = 0; i <= shortSupplyDays; i++) {
                            BigDecimal result = BigDecimal.valueOf(historySaleGTZero.longValue()).multiply(Constant.FBA_OUT_STOCK_DECREASE.pow(shortSupplyDays)).multiply(Constant.FBA_OUT_STOCK_INCREMENT.pow(i)).setScale(3, RoundingMode.HALF_EVEN);
                            if (j + i < forecastSalesList.size()) {
                                forecastSalesList.set(j + i - 1, result);
                                if (i == 0) {
                                    forecastSalesExplainList.set(j + i, "基于" + shortSupplyDays + "天断货累积递减");
                                } else {
                                    forecastSalesExplainList.set(j + i, "基于预测销量递增第" + i + "天");
                                }
                            }
                        }
                        shortSupplyIndex = -1;
                        shortSupplyDays = 0;
                    } else if (shortSupplyIndex > 0) {
                        //尽量降序找一个不为0的预测销量
                        BigDecimal forecastSaleGTZero = null;
                        for (int i = shortSupplyIndex; i >= 0; i--) {
                            forecastSaleGTZero = BigDecimal.ZERO;
                            if (forecastSalesList.get(i).compareTo(BigDecimal.ZERO) == 1) {
                                forecastSaleGTZero = forecastSalesList.get(i);
                                break;
                            }
                        }
                        //按照规则递减或递增 j = 63
                        for (int i = 0; i <= shortSupplyDays; i++) {
                            BigDecimal result = forecastSaleGTZero.multiply(Constant.FBA_OUT_STOCK_DECREASE.pow(shortSupplyDays)).multiply(Constant.FBA_OUT_STOCK_INCREMENT.pow(i)).setScale(3, RoundingMode.HALF_EVEN);
                            //索引越界
                            if (j + i < forecastSalesList.size()) {
                                forecastSalesList.set(j + i - 1, result);
                                if (i == 0) {
                                    forecastSalesExplainList.set(j + i, "基于" + shortSupplyDays + "天断货累积递减");
                                } else {
                                    forecastSalesExplainList.set(j + i, "基于预测销量递增第" + i + "天");
                                }
                            }
                        }
                        shortSupplyIndex = -1;
                        shortSupplyDays = 0;
                    }
                }
                forecastInventory = inboundDecimal.add(forecastInventoryList.get(j - 1)).subtract(forecastSalesList.get(j - 1));
                //为负的情况下, 应该为0
                if (forecastInventory.compareTo(BigDecimal.ZERO) == -1) {
                    forecastInventory = BigDecimal.ZERO;
                }
                //库存先补断货
                addShortSupply = forecastInventory.subtract(forecastShortSupplyList.get(j - 1));
                if (addShortSupply.compareTo(BigDecimal.ZERO) == -1) {
                    forecastInventory = BigDecimal.ZERO;
                    forecastShortSupplyList.set(j, addShortSupply.abs());
                } else {
                    forecastShortSupplyList.set(j, BigDecimal.ZERO);
                    forecastInventory = addShortSupply;
                }
                BigDecimal subtract = forecastSalesList.get(j).subtract(forecastInventory);//今日断货
                shortSupply = subtract.compareTo(BigDecimal.ZERO) == 1 ? subtract : BigDecimal.ZERO;
                forecastShortSupplyList.set(j, forecastShortSupplyList.get(j).add(shortSupply));
            }
            /*
            /*
             * 如果库存数量 > 安全库存 + 预计销量, 就是预测冗余;
             * 如果forecastShortSupplyList.get(j) > 0, 就是预测断货
             * 1实际断货2预计断货3实际冗余4预计冗余
             */
            if (j <= turnoverDays && hasMonitorFluctuation) {
                BigDecimal forecastShortSupplyDayJ = j == 0 ? forecastShortSupplyList.get(j) : forecastShortSupplyList.get(j).subtract(forecastShortSupplyList.get(j - 1));
                if (forecastInventory.compareTo(totalSafeInventory.add(forecastSalesList.get(j))) == -1) {
                    //预测断货, 需要检查前一天有没有预测冗余
                    if (forecastFluctuationDays > 0) {
                        checkForecastFluctuation(4, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
                        forecastFluctuationDays = 0;
                        forecastFluctuationIndex = -1;
                    }
                    forecastFluctuationDays--;
                    if (forecastFluctuationIndex == -1) {
                        forecastFluctuationIndex = j;
                    }
                } else if (forecastInventory.compareTo(totalSafeInventory.add(forecastSalesList.get(j))) == 1) {
                    //预测冗余, 需要检查前一天有没有预测断货
                    if (forecastFluctuationDays < 0) {
                        checkForecastFluctuation(2, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
                        forecastFluctuationDays = 0;
                        forecastFluctuationIndex = -1;
                    }
                    forecastFluctuationDays++;
                    if (forecastFluctuationIndex == -1) {
                        forecastFluctuationIndex = j;
                    }
                } else {
                    //既不断货又不冗余的情况下, 判断forecastFluctuationDays,forecastFluctuationIndex
                    judgeForecastFluctuation(forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
                    forecastFluctuationDays = 0;
                    forecastFluctuationIndex = -1;
                }
                if (j == turnoverDays) {
                    judgeForecastFluctuation(forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
                    forecastFluctuationDays = 0;
                    forecastFluctuationIndex = -1;
                }
            }
            //--------- 预测冗余, 预测断货 ---end
            forecastInventoryList.set(j, forecastInventory.compareTo(BigDecimal.ZERO) == -1 ? BigDecimal.ZERO : forecastInventory.setScale(3, RoundingMode.HALF_EVEN));
            //在两倍周转期内, 如果预测销量 <= 库存, 那么又是可用的一天
            if (j < Constant.TURNOVER_MULTIPLE * turnoverDays && forecastSalesList.get(j).compareTo(forecastInventory) != 1) {
                inventoryAvailableDays++;
            }

            //库存 - 销量, 继续跑, 这是周转期最后一天和周转期外的
            if (j >= turnoverDays) {
                if (j == turnoverDays) {            //周转期内总销量
                    dcAutoTurnover.setTurnoverSales(totalSales);
                }
                //采购建议 = 安全库存缺少值  + 今日的实际缺货  + 本次采购到货日期前一天的累计销量 - 本次采购到货日期累计到货数量 + 预计销量 - 今日的实际库存
                BigDecimal totalAdvised = totalSafeInventory.subtract(forecastInventoryList.get(j>0?j - 1:0))//安全库存缺少值= 理论安全库存  - 预测库存
                        .add(BigDecimal.valueOf(outStock.longValue()))//今天的实际缺货
                        .add(totalSales)//累计销量
                        .subtract(BigDecimal.valueOf(totalInbound.doubleValue()))//累计到货
                        .subtract(outStock > 0 ? BigDecimal.ZERO : BigDecimal.valueOf(realInventory.longValue())) //真实库存, 如果缺货就不减,不缺就减去
                        .add(forecastSalesList.get(j));//预计销量
                //采购建议数如果大于1的时候就向上取整。如果小于1就四舍五入
                if (totalAdvised.compareTo(BigDecimal.ONE) == 1) {
                    totalAdvised = totalAdvised.setScale(0, RoundingMode.CEILING);
                } else {
                    totalAdvised = totalAdvised.setScale(0, RoundingMode.HALF_EVEN);
                }
                forecastPurchaseAdvisedList.add(totalAdvised);
                if (totalAdvised.compareTo(moqDecimal) == 1) {
                    moqPurchaseAdvisedList.add(totalAdvised);
                } else if (totalAdvised.compareTo(BigDecimal.ZERO) != 1) {
                    moqPurchaseAdvisedList.add(BigDecimal.ZERO);
                } else {
                    moqPurchaseAdvisedList.add(moqDecimal);
                }
            }
            totalSales = totalSales.add(forecastSalesList.get(j));

        }
        return inventoryAvailableDays;
    }

    /**
     * 判断 预测冗余, 断货情况,
     * 然后放入集合
     *
     * @param forecastFluctuationList
     * @param recordTime
     * @param forecastFluctuationDays
     * @param forecastFluctuationIndex
     */
    private void judgeForecastFluctuation(List<DcAutoForecastFluctuation> forecastFluctuationList, LocalDate
            recordTime, int forecastFluctuationDays, int forecastFluctuationIndex) {
        DcAutoForecastFluctuation dcAutoForecastFluctuation = forecastFluctuationList.size() > 0 ? forecastFluctuationList.get(0) : null;
        if (dcAutoForecastFluctuation != null && dcAutoForecastFluctuation.getType() == 1 && forecastFluctuationDays > 0) {
            //已经提醒实际缺货的，预计缺货就不提醒了
            handleForecastFluctuation(4, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
        } else if (dcAutoForecastFluctuation != null && dcAutoForecastFluctuation.getType() == 3 && forecastFluctuationDays < 0) {
            //已经提醒实际冗余的，预计冗余就不提醒了
            handleForecastFluctuation(2, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
        } else if (dcAutoForecastFluctuation == null && forecastFluctuationDays > 0) {
            handleForecastFluctuation(4, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
        } else if (dcAutoForecastFluctuation == null && forecastFluctuationDays < 0) {
            handleForecastFluctuation(2, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
        }
    }

    private void checkForecastFluctuation(Integer type, List<DcAutoForecastFluctuation> forecastFluctuationList, LocalDate recordTime,
                                          int forecastFluctuationDays, int forecastFluctuationIndex) {
        DcAutoForecastFluctuation dcAutoForecastFluctuation = forecastFluctuationList.size() > 0 ? forecastFluctuationList.get(0) : null;
        if (dcAutoForecastFluctuation != null && dcAutoForecastFluctuation.getType() == 1 && type != 2) {
            //已经提醒实际缺货的，预计缺货就不提醒了
            handleForecastFluctuation(type, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
        } else if (dcAutoForecastFluctuation != null && dcAutoForecastFluctuation.getType() == 3 && type != 4) {
            //已经提醒实际冗余的，预计冗余就不提醒了
            handleForecastFluctuation(type, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
        } else if (dcAutoForecastFluctuation == null) {
            handleForecastFluctuation(type, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
        }
    }

    private void handleForecastFluctuation(Integer type, List<DcAutoForecastFluctuation> forecastFluctuationList, LocalDate recordTime, int forecastFluctuationDays, int forecastFluctuationIndex) {
        DcAutoForecastFluctuation forecastFluctuation = new DcAutoForecastFluctuation();
        forecastFluctuation.setStartTime(recordTime.plusDays(forecastFluctuationIndex + 1));
        forecastFluctuation.setEndTime(forecastFluctuation.getStartTime().plusDays(Math.abs(forecastFluctuationDays) - 2));
        forecastFluctuation.setType(type);
        forecastFluctuationList.add(forecastFluctuation);
    }

    private Integer commonAdvise(boolean hasMonitorFluctuation, List<DcAutoForecastFluctuation> forecastFluctuationList, LocalDate
            recordTime, DcAutoTurnover dcAutoTurnover, Integer turnoverDays, Integer
                                         autoForecastDay, List<BigDecimal> forecastInventoryList, List<Integer> forecastInboundList, List<BigDecimal> forecastSalesList, List<BigDecimal> forecastShortSupplyList, List<BigDecimal> forecastPurchaseAdvisedList, List<BigDecimal> moqPurchaseAdvisedList, Integer
                                         realInventory, BigDecimal totalSafeInventory, Integer outStock, Integer inventoryAvailableDays, BigDecimal
                                         totalSales, BigDecimal totalInbound, BigDecimal moqDecimal) {
        BigDecimal inboundDecimal;
        BigDecimal forecastInventory;
        BigDecimal shortSupply;
        BigDecimal addShortSupply;
        int forecastFluctuationDays = 0;//断货或冗余天数 + 1
        int forecastFluctuationIndex = -1;//断货或冗余索引
        for (int j = 0; j < autoForecastDay; j++) {
            inboundDecimal = BigDecimal.valueOf(forecastInboundList.get(j));
            totalInbound = totalInbound.add(inboundDecimal);
            if (j == turnoverDays) {
                dcAutoTurnover.setTurnoverInbound(totalInbound);
            }
            //计算预计库存 和 预计缺货
            if (j == 0) {
                if (outStock > 0) {
                    //如果有缺货数, 第一天预计库存 =  预计入库 - 缺货数
                    forecastInventory = BigDecimal.valueOf((forecastInboundList.get(j).doubleValue() - outStock.doubleValue()));
                    shortSupply = forecastInventory.compareTo(BigDecimal.ZERO) == -1 ? forecastInventory.abs() : BigDecimal.ZERO;//库存补入后的 缺货数
                    BigDecimal subtract = forecastSalesList.get(j).subtract(forecastInventory.compareTo(BigDecimal.ZERO) == 1 ? forecastInventory : BigDecimal.ZERO);//今天的缺货数
                    shortSupply = subtract.add(shortSupply).compareTo(BigDecimal.ZERO) == 1 ? subtract.add(shortSupply) : BigDecimal.ZERO;
                } else {
                    //否则, 第一天预计库存 =  真实库存 + 预计入库
                    forecastInventory = BigDecimal.valueOf((forecastInboundList.get(j).doubleValue() + realInventory.doubleValue()));
                    BigDecimal subtract = forecastSalesList.get(j).subtract(forecastInventory);
                    shortSupply = subtract.compareTo(BigDecimal.ZERO) == 1 ? subtract : BigDecimal.ZERO;
                }
                forecastShortSupplyList.set(j, forecastShortSupplyList.get(j).add(shortSupply));
            } else {
                forecastInventory = inboundDecimal.add(forecastInventoryList.get(j - 1)).subtract(forecastSalesList.get(j - 1));
                //库存先补断货
                //为负的情况下, 应该为0
                if (forecastInventory.compareTo(BigDecimal.ZERO) == -1) {
                    forecastInventory = BigDecimal.ZERO;
                }
                addShortSupply = forecastInventory.subtract(forecastShortSupplyList.get(j - 1));
                if (addShortSupply.compareTo(BigDecimal.ZERO) == -1) {
                    forecastInventory = BigDecimal.ZERO;
                    forecastShortSupplyList.set(j, addShortSupply.abs());
                } else {
                    forecastShortSupplyList.set(j, BigDecimal.ZERO);
                    forecastInventory = addShortSupply;
                }
                BigDecimal subtract = forecastSalesList.get(j).subtract(forecastInventory);//今日断货
                shortSupply = subtract.compareTo(BigDecimal.ZERO) == 1 ? subtract : BigDecimal.ZERO;
                forecastShortSupplyList.set(j, forecastShortSupplyList.get(j).add(shortSupply));
            }
            forecastInventoryList.set(j, forecastInventory.compareTo(BigDecimal.ZERO) == -1 ? BigDecimal.ZERO : forecastInventory);
            //在两倍周转期内, 如果预测销量 <= 库存, 那么又是可用的一天
            if (j < Constant.TURNOVER_MULTIPLE * turnoverDays && forecastSalesList.get(j).compareTo(forecastInventory) != 1) {
                inventoryAvailableDays++;
            }

            //库存 - 销量, 继续跑
            if (j >= turnoverDays) {
                if (j == turnoverDays) {
                    dcAutoTurnover.setTurnoverSales(totalSales);
                }
                //采购建议 = 安全库存缺少值  + 今日的实际缺货  + 本次采购到货日期前一天的累计销量 - 本次采购到货日期累计到货数量 + 预计销量
                BigDecimal totalAdvised = totalSafeInventory.subtract(forecastInventoryList.get(j>0?j - 1:0))//安全库存缺少值= 理论安全库存  - 预测库存
                        .add(BigDecimal.valueOf(outStock.longValue()))//今天的实际缺货
                        .add(totalSales)//累计销量
                        .subtract(BigDecimal.valueOf(totalInbound.doubleValue()))//累计到货
                        .subtract(outStock > 0 ? BigDecimal.ZERO : BigDecimal.valueOf(realInventory.longValue())) //真实库存,如果缺货就不减
                        .add(forecastSalesList.get(j));//预计销量
                //采购建议数如果大于1的时候就向上取整。如果小于1就四舍五入
                if (totalAdvised.compareTo(BigDecimal.ONE) == 1) {
                    totalAdvised = totalAdvised.setScale(0, RoundingMode.CEILING);
                } else {
                    totalAdvised = totalAdvised.setScale(0, RoundingMode.HALF_EVEN);
                }
                forecastPurchaseAdvisedList.add(totalAdvised);
                if (totalAdvised.compareTo(moqDecimal) == 1) {
                    moqPurchaseAdvisedList.add(totalAdvised);
                } else if (totalAdvised.compareTo(BigDecimal.ZERO) != 1) {
                    moqPurchaseAdvisedList.add(BigDecimal.ZERO);
                } else {
                    moqPurchaseAdvisedList.add(moqDecimal);
                }
            }

            totalSales = totalSales.add(forecastSalesList.get(j));

            /*
             * 如果库存数量 > 安全库存 + 预计销量, 就是预测冗余;
             * 如果forecastShortSupplyList.get(j) > 0, 就是预测断货
             */
            if (j <= turnoverDays && hasMonitorFluctuation) {
                if (forecastInventory.compareTo(totalSafeInventory.add(forecastSalesList.get(j))) == -1) {
                    //预测断货, 需要检查前一天有没有预测冗余
                    if (forecastFluctuationDays > 0) {
                        checkForecastFluctuation(4, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
                        forecastFluctuationDays = 0;
                        forecastFluctuationIndex = -1;
                    }
                    forecastFluctuationDays--;
                    if (forecastFluctuationIndex == -1) {
                        forecastFluctuationIndex = j;
                    }
                } else if (forecastInventory.compareTo(totalSafeInventory.add(forecastSalesList.get(j))) == 1) {
                    //预测冗余, 需要检查前一天有没有预测断货
                    if (forecastFluctuationDays < 0) {
                        checkForecastFluctuation(2, forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
                        forecastFluctuationDays = 0;
                        forecastFluctuationIndex = -1;
                    }
                    forecastFluctuationDays++;
                    if (forecastFluctuationIndex == -1) {
                        forecastFluctuationIndex = j;
                    }
                } else {
                    //既不断货又不冗余的情况下, 判断forecastFluctuationDays,forecastFluctuationIndex
                    judgeForecastFluctuation(forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
                    forecastFluctuationDays = 0;
                    forecastFluctuationIndex = -1;
                }
                if (j == turnoverDays) {
                    judgeForecastFluctuation(forecastFluctuationList, recordTime, forecastFluctuationDays, forecastFluctuationIndex);
                    forecastFluctuationDays = 0;
                    forecastFluctuationIndex = -1;
                }
            }
            //--------- 预测冗余, 预测断货 ---end
        }
        return inventoryAvailableDays;
    }


    /**
     * 获取调拨头程 + 调拨打包 + 海外仓入库天数 的配置
     *
     * @param bailunSku
     * @param warehouseCode
     * @param bailunFirstLevelCatagoryId
     * @return
     */
    private DcAutoConfigDelivery getDcAutoConfigDelivery(String bailunSku,String warehouseCode, DcBaseWarehouse dcBaseWarehouse, Integer
            bailunFirstLevelCatagoryId) {
        DcAutoConfigDelivery dcAutoConfigDelivery = null;
        //如果不是国内仓, 就读配置表
        if (dcBaseWarehouse != null && !dcBaseWarehouse.getHqType().equals(CommonConstant.DOMESTIC_WAREHOUSE)) {
            try {
                DcAutoConfigDeliveryMapper dcAutoConfigDeliveryMapper = SessionUtil.getSession().getMapper(DcAutoConfigDeliveryMapper.class);
                dcAutoConfigDelivery = dcAutoConfigDeliveryMapper.selectOneByExample(DcAutoConfigDeliveryExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunSku).andStatusEqualTo(1).andTypeEqualTo(1).example());
                if (dcAutoConfigDelivery == null && bailunFirstLevelCatagoryId != null) {
                    dcAutoConfigDelivery = dcAutoConfigDeliveryMapper.selectOneByExample(DcAutoConfigDeliveryExample.newAndCreateCriteria().andVariableCodeEqualTo(bailunFirstLevelCatagoryId.toString()).andStatusEqualTo(1).andTypeEqualTo(2).example());
                }
                if (dcAutoConfigDelivery == null) {
                    dcAutoConfigDelivery = dcAutoConfigDeliveryMapper.selectOneByExample(DcAutoConfigDeliveryExample.newAndCreateCriteria().andVariableCodeEqualTo(warehouseCode).andStatusEqualTo(1).andTypeEqualTo(3).example());
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Mybatis操作DB失败", e);
            } finally {
                SessionUtil.closeSession();
            }
        }
        if (dcAutoConfigDelivery == null) {
            dcAutoConfigDelivery = new DcAutoConfigDelivery(0, 0, 0);
        }
        return dcAutoConfigDelivery;
    }


    /**
     * 更新销量数据
     *
     * @param bailunSku
     * @param warehouseCode
     * @param forecastSalesList
     */
    private void assignmentToForecastSales(String bailunSku, String
            warehouseCode, List<BigDecimal> forecastSalesList) {
        if (forecastSalesList != null && forecastSalesList.size() > 0) {
            try {
                DcAutoSalesMapper autoSalesMapper1 = SessionUtil.getSession().getMapper(DcAutoSalesMapper.class);
                DcAutoSales dcAutoSales = autoSalesMapper1.selectOneByExample(DcAutoSalesExample.newAndCreateCriteria().andBailunSkuEqualTo(bailunSku).andWarehouseCodeEqualTo(warehouseCode).example());
                if (dcAutoSales != null) {
                    dcAutoSales.setDetails(forecastSalesList.toString());
                    dcAutoSales.setForecastTodaySales(forecastSalesList.get(0));
                    dcAutoSales.setForecastOnedaySales(forecastSalesList.get(1));
                    dcAutoSales.setForecastTwodaySales(forecastSalesList.get(2));
                    dcAutoSales.setForecastThreedaySales(forecastSalesList.get(3));
                    dcAutoSales.setForecastFourthdaySales(forecastSalesList.get(4));
                    dcAutoSales.setForecastFivedaySales(forecastSalesList.get(5));
                    dcAutoSales.setForecastSixdaySales(forecastSalesList.get(6));
                    dcAutoSales.setForecastSevendaySales(forecastSalesList.get(7));
                    dcAutoSales.setForecastEightdaySales(forecastSalesList.get(8));
                    dcAutoSales.setForecastNinedaySales(forecastSalesList.get(9));
                    dcAutoSales.setForecastTendaySales(forecastSalesList.get(10));
                    dcAutoSales.setForecastElevendaySales(forecastSalesList.get(11));
                    dcAutoSales.setForecastTwelvedaySales(forecastSalesList.get(12));
                    dcAutoSales.setForecastThridteendaySales(forecastSalesList.get(13));
                    dcAutoSales.setForecastFourteendaySales(forecastSalesList.get(14));
                    dcAutoSales.setForecastFifteendaySales(forecastSalesList.get(15));
                    dcAutoSales.setForecastSixteendaySales(forecastSalesList.get(16));
                    dcAutoSales.setForecastSeventeendaySales(forecastSalesList.get(17));
                    dcAutoSales.setForecastEighteendaySales(forecastSalesList.get(18));
                    dcAutoSales.setForecastNineteendaySales(forecastSalesList.get(19));
                    dcAutoSales.setForecastTwentydaySales(forecastSalesList.get(20));
                    dcAutoSales.setForecastTwentyOnedaySales(forecastSalesList.get(21));
                    dcAutoSales.setForecastTwentyTwodaySales(forecastSalesList.get(22));
                    dcAutoSales.setForecastTwentyThreedaySales(forecastSalesList.get(23));
                    dcAutoSales.setForecastTwentyFourthdaySales(forecastSalesList.get(24));
                    dcAutoSales.setForecastTwentyFivedaySales(forecastSalesList.get(25));
                    dcAutoSales.setForecastTwentySixdaySales(forecastSalesList.get(26));
                    dcAutoSales.setForecastTwentySevenedaySales(forecastSalesList.get(27));
                    dcAutoSales.setForecastTwentyEightdaySales(forecastSalesList.get(28));
                    dcAutoSales.setForecastTwentyNinedaySales(forecastSalesList.get(29));
                    dcAutoSales.setForecastThirtydaySales(forecastSalesList.get(30));
                    autoSalesMapper1.updateByPrimaryKeySelective(dcAutoSales);
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Mybatis操作DB失败", e);
            } finally {
                SessionUtil.closeSession();
            }
        }
    }


    private void assignmentToForecastShortSupply(String bailunSku, String
            warehouseCode, List<BigDecimal> forecastShortSupplyList) {
        if (forecastShortSupplyList != null && forecastShortSupplyList.size() > 0) {
            DcAutoShortSupply dcAutoShortSupply = new DcAutoShortSupply();
            dcAutoShortSupply.setBailunSku(bailunSku);
            dcAutoShortSupply.setWarehouseCode(warehouseCode);
            dcAutoShortSupply.setForecastTodayShortSupply(forecastShortSupplyList.get(0));
            dcAutoShortSupply.setForecastOnedayShortSupply(forecastShortSupplyList.get(1));
            dcAutoShortSupply.setForecastTwodayShortSupply(forecastShortSupplyList.get(2));
            dcAutoShortSupply.setForecastThreedayShortSupply(forecastShortSupplyList.get(3));
            dcAutoShortSupply.setForecastFourthdayShortSupply(forecastShortSupplyList.get(4));
            dcAutoShortSupply.setForecastFivedayShortSupply(forecastShortSupplyList.get(5));
            dcAutoShortSupply.setForecastSixdayShortSupply(forecastShortSupplyList.get(6));
            dcAutoShortSupply.setForecastSevendayShortSupply(forecastShortSupplyList.get(7));
            dcAutoShortSupply.setForecastEightdayShortSupply(forecastShortSupplyList.get(8));
            dcAutoShortSupply.setForecastNinedayShortSupply(forecastShortSupplyList.get(9));
            dcAutoShortSupply.setForecastTendayShortSupply(forecastShortSupplyList.get(10));
            dcAutoShortSupply.setForecastElevendayShortSupply(forecastShortSupplyList.get(11));
            dcAutoShortSupply.setForecastTwelvedayShortSupply(forecastShortSupplyList.get(12));
            dcAutoShortSupply.setForecastThridteendayShortSupply(forecastShortSupplyList.get(13));
            dcAutoShortSupply.setForecastFourteendayShortSupply(forecastShortSupplyList.get(14));
            dcAutoShortSupply.setForecastFifteendayShortSupply(forecastShortSupplyList.get(15));
            dcAutoShortSupply.setForecastSixteendayShortSupply(forecastShortSupplyList.get(16));
            dcAutoShortSupply.setForecastSeventeendayShortSupply(forecastShortSupplyList.get(17));
            dcAutoShortSupply.setForecastEighteendayShortSupply(forecastShortSupplyList.get(18));
            dcAutoShortSupply.setForecastNineteendayShortSupply(forecastShortSupplyList.get(19));
            dcAutoShortSupply.setForecastTwentydayShortSupply(forecastShortSupplyList.get(20));
            dcAutoShortSupply.setForecastTwentyOnedayShortSupply(forecastShortSupplyList.get(21));
            dcAutoShortSupply.setForecastTwentyTwodayShortSupply(forecastShortSupplyList.get(22));
            dcAutoShortSupply.setForecastTwentyThreedayShortSupply(forecastShortSupplyList.get(23));
            dcAutoShortSupply.setForecastTwentyFourthdayShortSupply(forecastShortSupplyList.get(24));
            dcAutoShortSupply.setForecastTwentyFivedayShortSupply(forecastShortSupplyList.get(25));
            dcAutoShortSupply.setForecastTwentySixdayShortSupply(forecastShortSupplyList.get(26));
            dcAutoShortSupply.setForecastTwentySevenedayShortSupply(forecastShortSupplyList.get(27));
            dcAutoShortSupply.setForecastTwentyEightdayShortSupply(forecastShortSupplyList.get(28));
            dcAutoShortSupply.setForecastTwentyNinedayShortSupply(forecastShortSupplyList.get(29));
            dcAutoShortSupply.setForecastThirtydayShortSupply(forecastShortSupplyList.get(30));
            dcAutoShortSupply.setDetails(forecastShortSupplyList.toString());
            try {
                DcAutoShortSupplyMapper autoShortSupplyMapper = SessionUtil.getSession().getMapper(DcAutoShortSupplyMapper.class);
                autoShortSupplyMapper.upsertSelective(dcAutoShortSupply);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Mybatis操作DB失败", e);
            } finally {
                SessionUtil.closeSession();
            }
        }
    }


    private void assignmentToForecastInbound(String bailunSku, String
            warehouseCode, Integer realInventory, List<Integer> forecastInboundList, List<String> forecastInboundRelationList) {
        DcAutoInbound dcAutoInbound = new DcAutoInbound();
        dcAutoInbound.setDetails(forecastInboundList.toString());
        dcAutoInbound.setRelation(forecastInboundRelationList.toString());
        dcAutoInbound.setBailunSku(bailunSku);
        dcAutoInbound.setWarehouseCode(warehouseCode);
        dcAutoInbound.setActualInventory(realInventory);
        dcAutoInbound.setForecastTodayInbound(forecastInboundList.get(0));
        dcAutoInbound.setForecastOnedayInbound(forecastInboundList.get(1));
        dcAutoInbound.setForecastTwodayInbound(forecastInboundList.get(2));
        dcAutoInbound.setForecastThreedayInbound(forecastInboundList.get(3));
        dcAutoInbound.setForecastFourthdayInbound(forecastInboundList.get(4));
        dcAutoInbound.setForecastFivedayInbound(forecastInboundList.get(5));
        dcAutoInbound.setForecastSixdayInbound(forecastInboundList.get(6));
        dcAutoInbound.setForecastSevendayInbound(forecastInboundList.get(7));
        dcAutoInbound.setForecastEightdayInbound(forecastInboundList.get(8));
        dcAutoInbound.setForecastNinedayInbound(forecastInboundList.get(9));
        dcAutoInbound.setForecastTendayInbound(forecastInboundList.get(10));
        dcAutoInbound.setForecastElevendayInbound(forecastInboundList.get(11));
        dcAutoInbound.setForecastTwelvedayInbound(forecastInboundList.get(12));
        dcAutoInbound.setForecastThridteendayInbound(forecastInboundList.get(13));
        dcAutoInbound.setForecastFourteendayInbound(forecastInboundList.get(14));
        dcAutoInbound.setForecastFifteendayInbound(forecastInboundList.get(15));
        dcAutoInbound.setForecastSixteendayInbound(forecastInboundList.get(16));
        dcAutoInbound.setForecastSeventeendayInbound(forecastInboundList.get(17));
        dcAutoInbound.setForecastEighteendayInbound(forecastInboundList.get(18));
        dcAutoInbound.setForecastNineteendayInbound(forecastInboundList.get(19));
        dcAutoInbound.setForecastTwentydayInbound(forecastInboundList.get(20));
        dcAutoInbound.setForecastTwentyOnedayInbound(forecastInboundList.get(21));
        dcAutoInbound.setForecastTwentyTwodayInbound(forecastInboundList.get(22));
        dcAutoInbound.setForecastTwentyThreedayInbound(forecastInboundList.get(23));
        dcAutoInbound.setForecastTwentyFourthdayInbound(forecastInboundList.get(24));
        dcAutoInbound.setForecastTwentyFivedayInbound(forecastInboundList.get(25));
        dcAutoInbound.setForecastTwentySixdayInbound(forecastInboundList.get(26));
        dcAutoInbound.setForecastTwentySevenedayInbound(forecastInboundList.get(27));
        dcAutoInbound.setForecastTwentyEightdayInbound(forecastInboundList.get(28));
        dcAutoInbound.setForecastTwentyNinedayInbound(forecastInboundList.get(29));
        dcAutoInbound.setForecastThirtydayInbound(forecastInboundList.get(30));
        try {
            DcAutoInboundMapper autoInboundMapper = SessionUtil.getSession().getMapper(DcAutoInboundMapper.class);
            autoInboundMapper.upsertSelective(dcAutoInbound);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Mybatis操作DB失败", e);
        } finally {
            SessionUtil.closeSession();
        }
    }


    private void assignmentToForecastInventory(String bailunSku, String warehouseCode, Integer
            realInventory, List<BigDecimal> forecastInventoryList) {
        if (forecastInventoryList != null && forecastInventoryList.size() > 0) {
            DcAutoInventory dcAutoInventory = new DcAutoInventory();
            dcAutoInventory.setBailunSku(bailunSku);
            dcAutoInventory.setWarehouseCode(warehouseCode);
            dcAutoInventory.setActualInventory(BigDecimal.valueOf(realInventory));
            dcAutoInventory.setForecastTodayInventory(forecastInventoryList.get(0));
            dcAutoInventory.setForecastOnedayInventory(forecastInventoryList.get(1));
            dcAutoInventory.setForecastTwodayInventory(forecastInventoryList.get(2));
            dcAutoInventory.setForecastThreedayInventory(forecastInventoryList.get(3));
            dcAutoInventory.setForecastFourthdayInventory(forecastInventoryList.get(4));
            dcAutoInventory.setForecastFivedayInventory(forecastInventoryList.get(5));
            dcAutoInventory.setForecastSixdayInventory(forecastInventoryList.get(6));
            dcAutoInventory.setForecastSevendayInventory(forecastInventoryList.get(7));
            dcAutoInventory.setForecastEightdayInventory(forecastInventoryList.get(8));
            dcAutoInventory.setForecastNinedayInventory(forecastInventoryList.get(9));
            dcAutoInventory.setForecastTendayInventory(forecastInventoryList.get(10));
            dcAutoInventory.setForecastElevendayInventory(forecastInventoryList.get(11));
            dcAutoInventory.setForecastTwelvedayInventory(forecastInventoryList.get(12));
            dcAutoInventory.setForecastThridteendayInventory(forecastInventoryList.get(13));
            dcAutoInventory.setForecastFourteendayInventory(forecastInventoryList.get(14));
            dcAutoInventory.setForecastFifteendayInventory(forecastInventoryList.get(15));
            dcAutoInventory.setForecastSixteendayInventory(forecastInventoryList.get(16));
            dcAutoInventory.setForecastSeventeendayInventory(forecastInventoryList.get(17));
            dcAutoInventory.setForecastEighteendayInventory(forecastInventoryList.get(18));
            dcAutoInventory.setForecastNineteendayInventory(forecastInventoryList.get(19));
            dcAutoInventory.setForecastTwentydayInventory(forecastInventoryList.get(20));
            dcAutoInventory.setForecastTwentyOnedayInventory(forecastInventoryList.get(21));
            dcAutoInventory.setForecastTwentyTwodayInventory(forecastInventoryList.get(22));
            dcAutoInventory.setForecastTwentyThreedayInventory(forecastInventoryList.get(23));
            dcAutoInventory.setForecastTwentyFourthdayInventory(forecastInventoryList.get(24));
            dcAutoInventory.setForecastTwentyFivedayInventory(forecastInventoryList.get(25));
            dcAutoInventory.setForecastTwentySixdayInventory(forecastInventoryList.get(26));
            dcAutoInventory.setForecastTwentySevenedayInventory(forecastInventoryList.get(27));
            dcAutoInventory.setForecastTwentyEightdayInventory(forecastInventoryList.get(28));
            dcAutoInventory.setForecastTwentyNinedayInventory(forecastInventoryList.get(29));
            dcAutoInventory.setForecastThirtydayInventory(forecastInventoryList.get(30));
            dcAutoInventory.setDetails(forecastInventoryList.toString());
            try {
                DcAutoInventoryMapper dcAutoInventoryMapper = SessionUtil.getSession().getMapper(DcAutoInventoryMapper.class);
                dcAutoInventoryMapper.upsertSelective(dcAutoInventory);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Mybatis操作DB失败", e);

            } finally {
                SessionUtil.closeSession();
            }
        }
    }


    /**
     * 给SKUMS发请求, 请求需加密
     *
     * @param catagoryId
     * @return
     * @throws Exception
     */
    String skuMSRequest(Integer catagoryId) {
        //请求签名
        //第一次MD5加密
        String str = DigestUtils.md5DigestAsHex(catagoryId.toString().getBytes());
        //第二次MD5加密
        String sign = DigestUtils.md5DigestAsHex((CommonConstant.SKU_APPKEY + CommonConstant.SKU_APPID + str).getBytes());
        HashMap<String, String> map = new HashMap<>(3);
        map.put("sign", sign);
        map.put("appId", CommonConstant.SKU_APPID);
        map.put("categoryId", catagoryId.toString());
        Request request = new Request.Builder()
                .url(OkHttpUtil.attachHttpGetParams(propertiesUtil.getPropertyAsString("SKUMS_PARENT_CATEGORIES_URL"), map))
                .get()
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            if (response != null && response.isSuccessful()) {
                JSONObject jsonNode = JSON.parseObject(response.body().string());
                Integer resultCode = jsonNode.getIntValue("result_code");
                String resultUrlSku = jsonNode.getString("data");
                if (resultCode != null && resultCode == 1) {
                    String resultSku = URLDecoder.decode(resultUrlSku, "utf8");
                    return resultSku;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("调用SKUMS获取百伦分类Id接口失败", e);
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return null;
    }

    public int getTotalPage(long count, Integer pageSize) {
        long totalPage;
        synchronized (AutoTurnoverJob.class) {
            if (count % pageSize.longValue() == 0) {
                totalPage = count / pageSize.longValue();
            } else {
                totalPage = count / pageSize.longValue() + 1;
            }
        }
        return (int) totalPage;
    }
}
