<?php
/**
 * Created by PhpStorm.
 * User: leo
 * Date: 2018/8/2
 * Time: 上午10:32
 */

namespace common\helpers\bonus;

use common\helpers\Cache;
use common\helpers\Date;
use common\helpers\LoggerTool;
use common\helpers\snowflake\SnowFake;
use common\helpers\Tool;
use common\models\BsBonus103Calc;
use common\models\BsBonus103Calc1;
use common\models\BsBonus103CalcNet;
use common\models\BsBonus103CalcNet1;
use common\models\BsBonus103Test;
use common\models\BsBonus103TestNet;
use common\models\CalcBonus;
use common\models\CalcBonusBD;
use common\models\CalcBonusBS;
use common\models\CalcBonusBsCalc;
use common\models\CalcBonusBsChecking;
use common\models\CalcBonusBsDetail;
use common\models\CalcBonusBsDetailBonus;
use common\models\CalcBonusBsDetailCalc;
use common\models\CalcBonusBsDetailGpv;
use common\models\CalcBonusBsStat;
use common\models\CalcBonusQuarter;
use common\models\CalcBonusGarage;
use common\models\CalcBonusQY;
use common\models\CalcBonusTG;
use common\models\CalcBonusTourism;
use common\models\CalcBonusVilla;
use common\models\Config;
use common\models\DeclarationLevel;
use common\models\DecOrder;
use common\models\EmployLevel;
use common\models\PerfMonth;
use common\models\PerfPeriod;
use common\models\Period;
use common\models\QtrCalcRecord;
use common\models\QtrCalcScore;
use common\models\QtrCalcUser;
use common\models\QtrCalcWeight;
use common\models\ServeLog;
use common\models\StarCrownLevel;
use common\models\User;
use Exception;
use Yii;
use yii\base\BaseObject;
use yii\base\StaticInstanceTrait;
use yii\db\Query;

class CalcServeBonusCalc extends BaseObject {
    use StaticInstanceTrait;

    private $_limit = 3000;
    private $_handleUserId;
    private $_sysConfig = [];
    private $_decLevelConfig = [];
    private $_empLevelConfig = [];
    private $_starCrownLevelConfig = [];
    private $_decRoleConfig = [];
    private $_errors = [];
    private $_periodNum = 0;
    private $_periodId;
    private $_isCalcMonth = 0;
    private $_calcYear;
    private $_calcMonth;
    private $_calcYearMonth;
    private $_calcMonthPeriodNumCount = 0;
    private $_isPerpare;
    //pv
    private $_pvRatio;
    private $_calcZone = ['openTravel', 'openCar', 'openHouse'];

    const LOOP_FINISH = 1;
    const LOOP_CONTINUE = 2;

    const ORDER_TYPE_TO_FW_COEFFICIENT = [
        'ZC' => 'fwCoefficientFromZc',
        'FX_CASH' => 'fwCoefficientFromFxCash',
        'FX_POINT' => 'fwCoefficientFromFxPoint',
    ];

    //最小报单pv
    const MIN_BD_PV = 980;

    public function init() {
        parent::init();
    }

    /**
     * 设置期数
     * @param int $periodNum
     * @return int
     */
    public function setPeriodNum(int $periodNum) {
        return $this->_periodNum = $periodNum;
    }

    /**
     * 获取期数
     * @return int
     */
    public function getPeriodNum() {
        return $this->_periodNum;
    }

    /**
     * 加入错误错误
     * @param $attr
     * @param $error
     */
    public function addError($attr, $error) {
        $this->_errors[$attr][] = $error;
    }

    /**
     * 获取错误信息
     * @return array
     */
    public function getErrors() {
        return $this->_errors;
    }

    /**
     * 开始执行结算步骤
     * @param $periodNum
     * @param null $handleUserId
     * @return bool
     */
    public function calcStep() {
        try {
            $this->_errors = [];
            $this->setPeriodNum(0);
            $this->_handleUserId = '';
            $t1 = microtime(true);
            // 初始化结算任务
            $this->initCalcTask();
            // 判断是否能开始进行计算奖金
            $checkStart = $this->checkStart();
            if (!$checkStart) {
                return false;
            } else {
                // 更新状态为,奖金计算中
                Period::updateCalcProcess(4, $this->_periodNum);
            }
            // 日志----记录开始
            ServeLog::noParamsLog($this->_periodNum, $this->_calcYearMonth, '开始奖金计算', 3000);
            // 设置结算状态
            $this->setCalcStatus('start');
            // 清空所有本期结算用到的缓存
            CalcCache::clearCalcBonusCache($this->_periodNum);
            
            // 日志----记录清除的流水表数据
            ServeLog::clearTableBonus($this->_periodNum, $this->_calcYearMonth, $this->_isCalcMonth);
            // 清空相关表数据
            $this->clearCalcTableData();
            $t4 = microtime(true);
            echo('初始化、清空缓存及相关数据表完成，耗时：' . round($t4 - $t1, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(10);
            // 蓝星奖放到最前面 奖金计算开始
            if($this->_sysConfig['openGL']['VALUE']) {
                echo('计算蓝星奖开始，' . date('Y-m-d H:i:s', $t4) . PHP_EOL);
                // 调用存储过程,计算蓝星管理奖金
                $this->calcBsProcedure();
                // 将有蓝星管理奖金的用户加入到有奖金缓存用户中
                $this->calcBonusBsGL();
            }
            // 日志----记录管理奖数据
            ServeLog::bonusBSData($this->_periodNum, $this->_calcYearMonth);
            $t5 = microtime(true);
            echo('计算蓝星奖'.($this->_sysConfig['openGL']['VALUE']?'完成':'关闭').'，耗时：' . round($t5 - $t4, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            
            // 更新期业绩表中用户的聘级
            if ($this->_isCalcMonth) {
                $this->loopUpdatePeriod();
            }

            // 更新月业绩表中用户的最新聘级
            if ($this->_isCalcMonth) {
                $this->loopUpdateMonthPerf();
            }

            // 月业绩表，增加个人小组业绩字段
            if ($this->_isCalcMonth) {
                $this->loopMonthPerfBsPGS();
            }
            // 日志----增加小组业绩数据
            ServeLog::perfMonthPgs($this->_periodNum, $this->_calcYearMonth);

            if($this->_sysConfig['openFW']['VALUE']) {
                $this->calcBonusBDStepOne();
                $this->calcBonusBDStepTwo();
            }
            // 日志----服务奖数据
            ServeLog::bonusBDData($this->_periodNum, $this->_calcYearMonth);
            $t6 = microtime(true);
            echo('计算服务奖'.($this->_sysConfig['openFW']['VALUE']?'完成':'关闭').'，耗时：' . round($t6 - $t5, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(15);

            // 销售奖/推广奖
            if($this->_sysConfig['openTG']['VALUE']) {
                $this->calcBonusTG();
            }
            // 日志----推广奖数据
            ServeLog::bonusTGData($this->_periodNum, $this->_calcYearMonth);
            $t7 = microtime(true);
            echo('计算推广奖'.($this->_sysConfig['openTG']['VALUE']?'完成':'关闭').'，耗时：' . round($t7 - $t6, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(20);

            // 绩效奖/团队奖
            if($this->_sysConfig['openQY']['VALUE']) {
                $this->calcBonusQY();
            }
            // 日志----团队奖数据
            ServeLog::bonusQYData($this->_periodNum, $this->_calcYearMonth);
            // 日志----团队奖计算会更新期业绩结余业绩字段
            ServeLog::noParamsLog($this->_periodNum, $this->_calcYearMonth, '更新期业绩表市场结余业绩完成AR_PERF_PERIOD', 3700);
            $t8 = microtime(true);
            echo('计算团队奖'.($this->_sysConfig['openQY']['VALUE']?'完成':'关闭').'，耗时：' . round($t8 - $t7, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(35);

//            $this->calcBonusTourism($this->_sysConfig['openTourism']);
            // $t21 = microtime(true);
//            echo('计算旅游奖' . ($this->_sysConfig['openTourism']['VALUE'] ? '完成' : '关闭') . '，耗时：' . round($t21 - $t20, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
//            $this->_updatePercent(68);
            if($this->_sysConfig['openVilla']['VALUE']) {
                $this->calcBonusVilla();
            }
            $t22 = microtime(true);
            echo('计算房奖' . ($this->_sysConfig['openVilla']['VALUE'] ? '完成' : '关闭').'，耗时：' . round($t22 - $t8, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(45);
            // 日志----房奖数据
            ServeLog::bonusVillaData($this->_periodNum, $this->_calcYearMonth);

            if($this->_sysConfig['openGarage']['VALUE']) {
                $this->calcBonusGarage();
            }
            // 日志----车奖数据
            ServeLog::bonusGarageData($this->_periodNum, $this->_calcYearMonth);
            $t23 = microtime(true);
            echo('计算车奖' . ($this->_sysConfig['openGarage']['VALUE'] ? '完成' : '关闭').'，耗时：' . round($t23 - $t22, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(55);

            // 计算季度奖
            if($this->_sysConfig['openQuarter']['VALUE']) {
                $this->calcQuarter();
            }
            $t24 = microtime(true);
            echo('计算季度奖' . ($this->_sysConfig['openQuarter']['VALUE'] ? '开启调用存储过程' : '关闭').'，耗时：' . round($t24 - $t23, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            // 将用户写入缓存
            if($this->_sysConfig['openQuarter']['VALUE']) {
                $this->calcQuarterUser();
            }
            // 日志----季度奖数据
            ServeLog::bonusQuarterData($this->_periodNum, $this->_calcYearMonth);
            $this->_updatePercent(65);
            $t25 = microtime(true);
            echo('计算季度奖' . ($this->_sysConfig['openQuarter']['VALUE'] ? '完成' : '关闭').'，耗时：' . round($t25 - $t24, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);

            // 奖金写库
            $this->loopBonusUsers();
            $this->_updatePercent(75);
            $t30 = microtime(true);
            // 日志----奖金写库
            ServeLog::noParamsLog($this->_periodNum, $this->_calcYearMonth, '写入奖金汇总表并计算管理费和复消积分', 4100);
            // 日志----写库数据统计
            ServeLog::bonusData($this->_periodNum, $this->_calcYearMonth);
            echo('奖金写库操作完成，耗时：' . round($t30 - $t25, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);

            $this->endCalcTask();
            $this->_updatePercent(100);
            $t35 = microtime(true);
            // 日志----计算完成
            ServeLog::noParamsLog($this->_periodNum, $this->_calcYearMonth, '奖金计算完成', 4300);
            echo('结算全部完成，共耗时：' . round($t35 - $t1, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
        } catch (\Exception $e) {
            $this->errorCalcTask();
            $this->addError('calc', sprintf('File【%s】, Line【%s】, Msg【%s】', $e->getFile(), $e->getLine(), $e->getMessage()));
            var_dump($e->getMessage());
            var_dump(sprintf('File【%s】, Line【%s】, Msg【%s】', $e->getFile(), $e->getLine(), $e->getMessage()));
            return false;
        }
        return true;
    }

    /**
     * 结算完成
     */
    public function endCalcTask() {
        $this->setCalcStatus('end');// 更新结算状态
    }

    /**
     * 结算错误
     */
    public function errorCalcTask() {
        CalcCache::clearCalcBonusCache($this->_periodNum); // 清空所有本期结算用到的缓存
        $this->setCalcStatus('fail'); // 更新结算状态
    }

    /**
     * 初始化结算任务
     * @throws \yii\db\Exception
     */
    public function initCalcTask() {
        $periodObj = Period::instance();
        $periodDataArr = $periodObj->setPeriodNum($this->_periodNum);
        if (empty($this->_periodNum)) {
            $this->_periodNum = $periodDataArr['PERIOD_NUM'];
        }
        $this->_sysConfig = Cache::getSystemConfig();
        $this->_decLevelConfig = Cache::getDecLevelConfig();
        $this->_empLevelConfig = Cache::getEmpLevelConfig();
        $this->_starCrownLevelConfig = Cache::getStarCrownLevelConfig();
        $this->_decRoleConfig = CalcCache::getDecRoleConfig($this->_periodNum);
        $this->_periodId = $periodDataArr['ID'];
        $this->_isPerpare = $periodDataArr['IS_PREPARE'];
        $this->_isCalcMonth = $periodObj->isCalcMonth($this->_periodNum);
        $this->_calcYear = $periodObj->getYear($this->_periodNum);
        $this->_calcMonth = $periodObj->getMonth($this->_periodNum);
        $this->_calcYearMonth = $periodObj->getYearMonth($this->_periodNum);
    }

    // 校验是否能开始进行计算
    public function checkStart() {
        if ($this->_isPerpare == 3) {
            return true;
        }

        return false;
    }

    /**
     * 设置结算状态
     * @param $type
     * start|end|fail
     */
    public function setCalcStatus($type) {
        if ($type == 'start') {
            Period::updateAll(['IS_CALCING' => 1, 'IS_CALCULATED' => Period::CALCULATE_NONE, 'CALCULATE_STARTED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
        } elseif ($type == 'end') {
            Period::updateAll(['IS_CALCING' => 0, 'IS_CALCULATED' => Period::CALCULATE_FINISH, 'CALCULATED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
        } elseif ($type == 'fail') {
            Period::updateAll(['IS_CALCING' => 0, 'IS_CALCULATED' => Period::CALCULATE_FAIL, 'CALCULATED_AT' => 0], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
        }
    }

    /**
     * 清空相关表数据
     */
    public function clearCalcTableData() {
        try {
            // 奖金表
            CalcBonus::pageDeleteAll('1=1');
            CalcBonusQY::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
            CalcBonusBD::pageDeleteAll('1=1'); // 实际上是服务奖流水表
            CalcBonusTG::pageDeleteAll('1=1');
            // 月结时要清空的数据
            if ($this->_isCalcMonth) {
                CalcBonusTourism::pageDeleteAll('1=1');
                CalcBonusGarage::pageDeleteAll('1=1');
                CalcBonusVilla::pageDeleteAll('1=1');
                //计算服务不存储数据,存储过程表清空
                BsBonus103Calc::deleteAll();
                BsBonus103Calc1::deleteAll();
                BsBonus103CalcNet::deleteAll();
                BsBonus103CalcNet1::deleteAll();
                BsBonus103Test::deleteAll();
                BsBonus103TestNet::deleteAll();
                CalcBonusBsCalc::deleteAll();
                CalcBonusBsChecking::deleteAll();
                CalcBonusBsDetail::deleteAll();
                CalcBonusBsDetailBonus::deleteAll();
                CalcBonusBsDetailCalc::deleteAll();
                CalcBonusBsDetailGpv::deleteAll();
                CalcBonusBsStat::deleteAll();
                QtrCalcUser::deleteAll();
                QtrCalcScore::deleteAll();
                QtrCalcRecord::deleteAll();
                QtrCalcWeight::deleteAll();
                CalcBonusBS::deleteAll();
                CalcBonusTourism::deleteAll();
                CalcBonusVilla::deleteAll();
            }
        } catch(\Exception $e) {
            var_dump($e->getMessage(), '------------');
            exit;
        }
    }

    // 修正期业绩表中,用户的最新聘级字段
    public function loopUpdatePeriod($offset = 0) {
        echo sprintf("时间:[%s]修正期业绩表最新聘级字段，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        $allData = PerfPeriod::findUseDbCalc()
        ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
        ->offset($offset)
        ->limit($this->_limit)
        ->asArray()
        ->all();
        if ($allData) {
            foreach ($allData as $data) {
                $nowBsEmpLv = $this->_nowLastEmpLv($data['USER_ID']);
                // 期结算结果
                PerfPeriod::updateAll(['LAST_EMP_LV' => $nowBsEmpLv], 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', 
                    [
                        ':USER_ID' => $data['USER_ID'],
                        ':PERIOD_NUM' => $this->_periodNum
                    ]
                );
            }
            unset($allData);
            return $this->loopUpdatePeriod($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }

    // 修正月业绩表中,用户最新聘级
    public function loopUpdateMonthPerf($offset = 0) {
        echo sprintf("时间:[%s]修正月业绩表最新聘级字段，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        $allData = PerfMonth::findUseDbCalc()
        ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
        ->offset($offset)
        ->limit($this->_limit)
        ->asArray()
        ->all();
        if ($allData) {
            foreach ($allData as $data) {
                $nowBsEmpLv = $this->_nowLastEmpLv($data['USER_ID']);
                // 期结算结果
                PerfMonth::updateAll(['LAST_EMP_LV' => $nowBsEmpLv], 'USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', 
                    [
                        ':USER_ID' => $data['USER_ID'],
                        ':CALC_MONTH' => $this->_calcYearMonth
                    ]
                );
            }
            unset($allData);
            return $this->loopUpdateMonthPerf($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }

    // 月业绩表，增加个人小组业绩字段
    // 此字段根据AR_CALC_BONUS_BS_DETAIL中GPV10字段来更新用户，如果月业绩表中无此用户，则新增一条数据。
    public function loopMonthPerfBsPGS($offset = 0) {
        echo sprintf("时间:[%s]月业绩表个人小组业绩更新，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        $allData = CalcBonusBsDetail::findUseDbCalc()
        ->where('PERIOD_NUM=:PERIOD_NUM AND CALC_MONTH=:CALC_MONTH AND GPV10 > 0', [':PERIOD_NUM' => $this->_periodNum, ':CALC_MONTH' => $this->_calcYearMonth])
        ->offset($offset)
        ->limit($this->_limit)
        ->asArray()
        ->all();
        if ($allData) {
            foreach ($allData as $data) {
                $insertData=[];
                // 查询月业绩表中是否有此用户数据
                $hasMonthPerfInfo = PerfMonth::find()
                ->where('USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', [':USER_ID'=>$data['USER_ID'], ':CALC_MONTH' => $this->_calcYearMonth])
                ->asArray()
                ->one();
                if ($hasMonthPerfInfo) {
                    //存在数据,则更新月业绩表中的个人小组业绩
                    // 期结算结果
                    PerfMonth::updateAll(['DIRECTOR_BONUS_PGS' => $data['GPV10']], 'USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', 
                    [
                        ':USER_ID' => $data['USER_ID'],
                        ':CALC_MONTH' => $this->_calcYearMonth
                    ]);
                } else {
                    // 如果不存在,则月业绩表中添加一条数据进去
                    $insertData[] = [
                        'USER_ID' => $data['USER_ID'],
                        'DIRECTOR_BONUS_PGS' => $data['GPV10'],
                        'CALC_MONTH' => $this->_calcYearMonth,
                        'CREATED_AT' => Date::nowTime()
                    ];
                    PerfMonth::batchInsert($insertData);
                }
            }
            unset($allData);
            return $this->loopMonthPerfBsPGS($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }

    /**
     * 推广奖
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function calcBonusTG(int $offset = 0) {
        $periodNum = $this->_periodNum;
        // 从缓存获取分页有业绩的会员信息
        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
        if ($allData) {
            $insertBonusData = [];
            foreach ($allData as $userId) {
                // 从缓存中获取会员的业绩信息
                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
                if( !$perfData ) continue;
                //个人业绩都算推荐奖，包括报单和复消、二次购物
                $perfPv = $perfData['PV_PCS_ZC'] ?? 0;
                if( $perfPv <= 0 ) continue;

                //推广奖使用个人PCS
                $recBonus = Tool::formatPrice($perfPv * $this->_sysConfig['recPercent']['VALUE'] / 100);
                if ($recBonus <= 0) continue;
                // 把对碰后的奖金存入缓存中
                $perfUserInfo = CalcCache::getUserInfo($userId, $periodNum);
                $bonusUserId = $perfUserInfo['REC_UID'] ?? '';
                if( !$bonusUserId ) continue;

                // 获取会员的报单级别
                $userBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
                CalcCache::bonus($bonusUserId, $this->_periodNum, 'BONUS_TG', $recBonus);                

                //来源会员信息
                $fromUserInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
                //推广奖流水
                $insertBonusData[] = [
                    'ID' => SnowFake::instance()->generateId(),
                    'USER_ID' => $bonusUserId,
                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
                    'LAST_EMP_LV' => $this->_nowLastEmpLv($bonusUserId),
                    'LAST_STATUS' => $userBaseInfo['STATUS'],
                    'FROM_USER_ID' => $userId,
                    'LAST_FROM_DEC_LV' => $fromUserInfo['DEC_LV'],
                    'LAST_FROM_EMP_LV' => $fromUserInfo['EMP_LV'],
                    'LAST_FROM_STATUS' => $fromUserInfo['STATUS'],
                    'AMOUNT' => $recBonus,
                    'ORI_BONUS' => $recBonus,
                    'PERIOD_NUM' => $this->_periodNum,
                    'CALC_YEAR' => $this->_calcYear,
                    'CALC_MONTH' => $this->_calcYearMonth,
                    'CREATED_AT' => Date::nowTime(),
                    'LOGS' => json_encode([
                        'perfPv' => $perfPv,
                        'recPercentConfig' => $this->_sysConfig['recPercent']['VALUE'],
                        'decLevel' => $userBaseInfo['DEC_LV'],
                    ]),
                ];

                unset($perfData, $perfPv, $perfUserInfo, $recBonus, $bonusUserId, $userBaseInfo, $userId, $deductData, $fromUserInfo);
            }
            CalcBonusTG::batchInsert($insertBonusData);
            unset($allData, $insertBonusData);
            return $this->calcBonusTG($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }

    /**
     * 服务奖第一步
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function calcBonusBDStepOne(int $offset = 0) {
        echo sprintf("时间:[%s]服务奖第【1】步，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        $periodNum = $this->_periodNum;
        // 从缓存获取分页有业绩的会员信息
        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
        if ($allData) {
            $insertBonusData = [];
            foreach ($allData as $userId) {
                // 从缓存中获取会员的业绩信息
                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
                if( !$perfData ) continue;

                // 服务奖来源
                $decRoleBonusFrom = explode(',', $this->_sysConfig['decRoleBonusFrom']['VALUE']);
                $validPvPcs = 0;
                foreach ($decRoleBonusFrom as $orderType) {
                    $orderTypeName = sprintf('PV_PCS_%s', $orderType);
                    $orderTypeValue = $perfData[$orderTypeName] ?? 0;

                    $coefficientName = self::ORDER_TYPE_TO_FW_COEFFICIENT[$orderType];
                    $coefficient = $this->_sysConfig[$coefficientName]['VALUE'] ?? 1;
                    $validPvPcs += $orderTypeValue * $coefficient;

                    unset($orderType, $orderTypeName, $orderTypeValue, $coefficientName, $coefficient);
                }
                unset($perfData, $decRoleBonusFrom);
                if ($validPvPcs <= 0) {
                    continue;
                }

                // 会员被报单时的报单中心
                $bonusUserId = DecOrder::find()->where('TO_USER_ID=:TO_USER_ID', [':TO_USER_ID' => $userId])->select('DEC_ID')->orderBy('CREATED_AT DESC')->scalar();
                // 判断报单中心级别 和 服务奖比例
                // 计算级别之后更新过userInfo的缓存,缓存中级别发生了变化
                $bonusUserInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
                $isDec = $bonusUserInfo['IS_DEC'];
                if ($isDec == 0) {
                    continue;
                }
                // 报单中心等级
                $decRoleId = $bonusUserInfo['DEC_ROLE_ID'];
                if (!$decRoleId || !isset($this->_decRoleConfig[$decRoleId])) {
                    continue;
                }

                // 计算奖金比例
                $decRoleLevel = $this->_decRoleConfig[$decRoleId];
                $fwBonusPercent = $decRoleLevel['FW_BONUS_PERCENT'] ?? 0;
                $cacheMaxPercent = CalcCache::fwMaxBonusPercent($bonusUserId, $this->_periodNum);
                $diffPercent = $fwBonusPercent - $cacheMaxPercent;
                if ($diffPercent <= 0) {
                    continue;
                }
                // 计算奖金
                $fwBonus = $validPvPcs * $diffPercent / 100;

                // 给本人添加服务奖比例
                CalcCache::fwMaxBonusPercent($bonusUserId, $this->_periodNum, $fwBonusPercent);
                // 记录奖金和奖金来源到缓存 并实现在缓存中奖金累加
                CalcCache::saveFwBonusList($bonusUserId, $this->_periodNum, $fwBonus, ['fromUid'=>$userId, 'fromPvPcs'=>$validPvPcs]);
                CalcCache::addHasFwBonusUsers($bonusUserId, $this->_periodNum);

                unset($userId, $validPvPcs);
            }

            unset($allData, $insertBonusData);
            return $this->calcBonusBDStepOne($offset + $this->_limit);
        }

        unset($allData);
        return true;
    }

    /**
     * 服务奖第二步
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function calcBonusBDStepTwo(int $offset = 0) {
        echo sprintf("时间:[%s]服务奖第【2】步，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);

        $allData = CalcCache::getHasFwBonusUsers($this->_periodNum, $offset, $this->_limit);
        if ($allData) {
            $insertBonusData = [];
            foreach ($allData as $userId) {
                $fwBonusData = CalcCache::getFwBonusList($userId, $this->_periodNum);
                if( !$fwBonusData ) continue;
                $fwBonus = $fwBonusData['fwBonus'] ?? 0;
                if( $fwBonus <=0  ) continue;
                //总金额限制
                try {
                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
                CalcCache::bonus($userId, $this->_periodNum, 'BONUS_BD', $fwBonus);
                 
                $decRoleId = $userBaseInfo['DEC_ROLE_ID'];
                $insertBonusData[] = [
                    'ID' => SnowFake::instance()->generateId(),
                    'USER_ID' => $userId,
                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
                    'LAST_EMP_LV' => $this->_nowLastEmpLv($userId),
                    'LAST_STATUS' => $userBaseInfo['STATUS'],
                    'FROM_USER_ID' => $userId,
                    'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
                    'LAST_FROM_EMP_LV' => $userBaseInfo['EMP_LV'],
                    'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
                    'AMOUNT' => $fwBonus,
                    'ORI_BONUS' => $fwBonus,
                    'RECONSUME_POINTS' => 0,
                    'MANAGE_TAX' => 0,
                    'PERIOD_NUM' => $this->_periodNum,
                    'CALC_YEAR' => $this->_calcYear,
                    'CALC_MONTH' => $this->_calcYearMonth,
                    'CREATED_AT' => Date::nowTime(),
                    'LOGS' => json_encode([
                        'decRoleId' => $decRoleId,
                    ])
                ];
                } catch(Exception $e) {
                    var_dump('---->>>', $e->getMessage());exit;
                }

                unset($userId, $fwBonusData, $userBaseInfo, $decRoleId, $fwBonus);
            }

            CalcBonusBD::batchInsert($insertBonusData);
            unset($insertBonusData, $allData);
            $this->calcBonusBDStepTwo($offset + $this->_limit);
        }

        unset($allData);
        return true;
    }

    /**
     * 团队奖
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function calcBonusQY(int $offset = 0) {
        echo sprintf("时间:[%s]团队奖，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        $periodNum = $this->_periodNum;
        // 从缓存获取分页有业绩的会员信息
        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
        if ($allData) {
            $insertBonusData = [];
            foreach ($allData as $userId) {
                // 从缓存中获取会员的业绩信息
                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
                // 从缓存中获取会员的上期结余业绩信息
                $pervSurplusPerf = CalcCache::surplusPerf($userId, $periodNum);
                // 本期 + 上期结余
                $perfArr = [
                    'SURPLUS_1L' => $perfData['PV_1L_TOUCH'] + $pervSurplusPerf['SURPLUS_1L'],
                    'SURPLUS_2L' => $perfData['PV_2L_TOUCH'] + $pervSurplusPerf['SURPLUS_2L'],
                    'SURPLUS_3L' => $perfData['PV_3L_TOUCH'] + $pervSurplusPerf['SURPLUS_3L'],
                    'SURPLUS_4L' => $perfData['PV_4L_TOUCH'] + $pervSurplusPerf['SURPLUS_4L'],
                    'SURPLUS_5L' => $perfData['PV_5L_TOUCH'] + $pervSurplusPerf['SURPLUS_5L'],
                ];
                $oriPerfArr = [
                    'perfArr' => $perfArr,
                    'touchBonus' => 0,
                ];

                // 获取会员的报单级别
                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
                $decLevelConfig = $this->_decLevelConfig;
                $nowDecLevelConfig = $decLevelConfig[$userBaseInfo['DEC_LV']];
                // 对碰
                $touchBonusArr = $this->touchPerf($oriPerfArr, $perfArr, $nowDecLevelConfig['QY_PERCENT']/100);
                $touchPerfArr = [];
                foreach ($touchBonusArr['perfArr'] as $keyR => $perfR) {
                    $touchPerfArr[$keyR] = $perfR;
                }
                // 对碰完成后把结余的业绩存入本期业绩缓存中
                CalcCache::nowPeriodPerf($userId, $periodNum, $touchPerfArr);
                //更新数据库
                PerfPeriod::updateAll($touchPerfArr, 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', [
                    'USER_ID' => $userId,
                    'PERIOD_NUM' => $periodNum,
                ]);
                if ($touchBonusArr['touchBonus'] <= 0) continue;

                $teamBonus = $touchBonusArr['touchBonus'];
                $capBonusQy = $teamBonus; // 封顶前的奖金
                //判断级别上限,个人奖金封顶限制
                $teamBonus = $this->declarationLevelCap($teamBonus, $userId, $userBaseInfo['DEC_LV']);
                if( $teamBonus <= 0 ) continue;
                // 将封顶前的金额加入用户奖金缓存中,此金额不能发放(总奖金,总实际奖金) 
                CalcCache::bonus($userId, $periodNum, 'CAPPED_BONUS_QY', $capBonusQy); 

                // TODO：取小腿值
                $payLeg = min([$perfArr['SURPLUS_1L'], $perfArr['SURPLUS_2L']]);
                // 计算荣衔星级
                $starCrown = StarCrownLevel::getStarCrown($payLeg);

                // 是否活跃
                $isActive = User::isPerfActive($userId, $this->_periodNum, $this->_calcYearMonth);
                $oriBonus = $isActive ? $teamBonus : 0;
                $lastCrownLv = $isActive ? $starCrown['ID'] : StarCrownLevel::getDefaultLevelId();
                
                //团队奖流水
                $insertBonusData[] = [
                    'ID' => SnowFake::instance()->generateId(),
                    'USER_ID' => $userId,
                    'ORI_CAPPED_BONUS_QY' => $capBonusQy,
                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
                    'LAST_EMP_LV' => $this->_nowLastEmpLv($userId),
                    'LAST_CROWN_LV' => $lastCrownLv,
                    'LAST_STATUS' => $userBaseInfo['STATUS'],
                    'AMOUNT' => $oriBonus,
                    'ORI_BONUS' => $oriBonus,
                    'PERIOD_NUM' => $this->_periodNum,
                    'CALC_YEAR' => $this->_calcYear,
                    'CALC_MONTH' => $this->_calcYearMonth,
                    'CREATED_AT' => Date::nowTime(),
                    'LOGS' => json_encode([
                        'perfArr' => $perfArr,
                        'touchPerfArrOri' => $touchBonusArr['perfArr'],
                        'touchPerfArr' => $touchPerfArr,
                        'nowDecLevelConfig' => $nowDecLevelConfig,
                        'decLevel' => $userBaseInfo['DEC_LV'],
                    ]),
                    'IS_ACTIVE' => (int)$isActive,
                    'HOPE_CROWN_LV' => $starCrown['ID'],
                    'HOPE_BONUS' => $teamBonus,
                ];
                // 星级放入缓存
                CalcCache::addUserStarCrown($userId, $periodNum, $lastCrownLv);
                if ($oriBonus > 0) {
                    // 把对碰后的奖金存入缓存中
                    // CalcCache::bonus($userId, $periodNum, 'BONUS_QY', $oriBonus, $deductData);
                    CalcCache::bonus($userId, $periodNum, 'BONUS_QY', $teamBonus);
                }

                unset($perfData, $pervSurplusPerf, $perfArr, $oriPerfArr, $touchPerfArr, $userBaseInfo, $decLevelConfig, $touchBonusArr, $userId, $nowDecLevelConfig, $teamBonus, $deductData);
            }
            CalcBonusQY::batchInsert($insertBonusData);
            unset($allData, $insertBonusData);
            return $this->calcBonusQY($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }

    /**
     * 季度奖计算
     */
    public function calcQuarter() {
        if( !$this->_isCalcMonth || !in_array($this->_calcMonth, [3,6,9,12])) {
        // echo('不是季结点，进这里，不计算季度奖'. PHP_EOL);
            return false;
        }
        $result = \Yii::$app->db->createCommand("CALL QtrCalc(:periodNum)")
            ->bindValue(':periodNum' , $this->_periodNum )
            ->execute();

        return $result;
    }

    // 执行蓝星管理奖金的存储过程
    public function calcBsProcedure() {
        if( !$this->_isCalcMonth ) {
            // 不是结算月,则不进行计算
            return false;
        }
        $result = \Yii::$app->db->createCommand("CALL CalcBlue(:periodNum)") 
                      ->bindValue(':periodNum' , $this->_periodNum )
                      ->execute();
                      
        return $result;
    }

    // 执行旅游奖的计算
    public function calcBonusTourism() {
        // 月结，如果不是月结点，则直接退出
        if (!$this->_isCalcMonth) {
            return true;
        }

        $bonusConfig = $this->_sysConfig['openTourism'];
        // 达标条件：聘级、级别、奖项比例
        $config = json_decode($bonusConfig['OPTIONS'], true);
        // 奖金总比例
        $mate = $bonusConfig['VALUE'] / 100;
        // 会员级别
        $minDecLevel = $config['OPTIONS']['declarationLevel'] ?? [];
        // 月度公司总PV
        $monthTotalPV = PerfMonth::find()
            ->yearMonth($this->_calcYearMonth)
            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
            ->sum('PV_PCS');
        // 用于分发的奖金总数
        $transferAmount = $monthTotalPV * $mate;

        // 基于蓝星奖结果计算符合获奖条件的会员StarDirector
        $userStarDirector = CalcBonusBS::find()
            ->yearMonth($this->_calcYearMonth)
            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
            ->select('USER_ID,LEVEL_ID,LAST_DEC_LV,LAST_EMP_LV,LAST_STATUS')
            ->groupBy('USER_ID')
            ->asArray()
            ->all();
        $userStarDirectorObj = array_column($userStarDirector, NULL, 'USER_ID');

        // 基于团队奖/绩效奖结果计算会员的StarCrown
        $userStarCrown = CalcBonusQY::find()
            ->yearMonth($this->_calcYearMonth)
            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
            ->select('USER_ID,LAST_CROWN_LV')
            ->groupBy('USER_ID')
            ->asArray()
            ->all();
        $userStarCrownObj = array_column($userStarCrown, NULL, 'USER_ID');

        // 合并用户ID,去重
        $bonusUsers = array_unique(array_merge(array_keys($userStarDirectorObj), array_keys($userStarCrownObj)));

        // 奖金点数综合
        $bonusPointComplex = 0;
        $insertBonusData = [];
        foreach($bonusUsers as $userId) {
            // 计算奖金：取starDirectorPoint和starCrownPoint的大个值
            $starDirectorPoint = $this->_empLevelConfig[$userStarDirectorObj[$userId]['LEVEL_ID']]['TOURISM_PERCENT'] ?? 0;
            $starCrownPoint = $this->_starCrownLevelConfig[$userStarCrownObj[$userId]['LAST_CROWN_LV']]['TOURISM_PERCENT'] ?? 0;
            // 奖金比例：
            $bonusPoint = max($starDirectorPoint, $starCrownPoint);
            if ($bonusPoint <= 0) {
                continue;
            }
            
            $insertBonusData[] = [
                'ID' => SnowFake::instance()->generateId(),
                'USER_ID' => $userId,
                'LAST_DEC_LV' => $userStarDirectorObj[$userId]['LAST_DEC_LV'],
                'LAST_EMP_LV' => $userStarDirectorObj[$userId]['LEVEL_ID'],
                'LAST_STATUS' => $userStarDirectorObj[$userId]['LAST_STATUS'],
                'LAST_CROWN_LV' => $userStarCrownObj[$userId]['LAST_CROWN_LV'],
                'AMOUNT_STANDARD' => 0,
                'POINT' => $bonusPoint,
                'PERIOD_NUM' => $this->_periodNum,
                'CALC_YEAR' => $this->_calcYear,
                'CALC_MONTH' => $this->_calcYearMonth,
                'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
                'CREATED_AT' => Date::nowTime(),
                'PERF' => $monthTotalPV,
                'TRANSFER_RATE' => $mate,
                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
                'CAP_AMOUNT' => 0,
                'POINT_COMPLEX' => 0,
            ];

            $bonusPointComplex += $bonusPoint;
        }

        // 数据写入总表
        if ($insertBonusData) {
            foreach ($insertBonusData as &$bonusData) {
                // 计算奖金
                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
                if ($amount <= 0) {
                    continue;
                }

                // 会员级别达到要求才会发放奖金
                if ($bonusData['LAST_DEC_LV'] == $minDecLevel) {
                    // 放入缓存
                    CalcCache::tourismBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
                }
                $bonusData['AMOUNT'] = $amount;
                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;
            }

            CalcBonusTourism::batchInsert($insertBonusData);
        }

        return true;
    }

    // 执行房奖的计算
    public function calcBonusVilla() {
        if (!$this->_isCalcMonth) {
            return true;
        }

        $bonusConfig = $this->_sysConfig['openVilla'];
        // 达标条件：聘级、级别、奖项比例
        $config = json_decode($bonusConfig['OPTIONS'], true);
        // 奖金总比例
        $mate = $bonusConfig['VALUE'] / 100;
        // 个人奖金封顶
        $capBonus = intval($this->_sysConfig['openVillaCap']['VALUE'] ?? 0);
        // 会员级别
        $minDecLevel = $config['declarationLevel'] ?? [];

        // 月度公司总PV
        $monthTotalPV = PerfMonth::find()
            ->yearMonth($this->_calcYearMonth)
            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
            ->sum('PV_PCS');
        // 用于分发的奖金总数
        $transferAmount = $monthTotalPV * $mate;

        // 基于团队奖/绩效奖结果计算会员的StarCrown.StarCrown基于周期计算,一个月会产生多次,取月周期中的最高星级
        $subQuery = CalcBonusQY::find()
            ->yearMonth($this->_calcYearMonth)
            ->where('CALC_MONTH = :CALC_MONTH AND LAST_CROWN_LV <> :NO_CROWN_LV', [':CALC_MONTH' => $this->_calcYearMonth, ':NO_CROWN_LV' => StarCrownLevel::NO_LEVEL_ID])
            ->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')
            ->joinWith(['starCrown' => function($query) {
                $query->select(['LEVEL_NAME', 'SORT']);
            }])
            ->having(1)
            ->orderBy('USER_ID ASC, SORT DESC');
        $userStarCrownObj = (new Query())->from(['u' => $subQuery])->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')->groupBy('USER_ID')->indexBy('USER_ID')->all();

        // 奖金点数综合
        $bonusPointComplex = 0;
        $insertBonusData = [];
        foreach($userStarCrownObj as $item) {
            // 奖金比例
            $bonusPoint = $this->_starCrownLevelConfig[$item['LAST_CROWN_LV']]['VILLA_PERCENT'] ?? 0;
            if (!$bonusPoint) {
                continue;
            }

            // 会员级别达到要求才会发放奖金
            if ($item['LAST_DEC_LV'] != $minDecLevel) {
                continue;
            }
            $insertBonusData[] = [
                'ID' => SnowFake::instance()->generateId(),
                'USER_ID' => $item['USER_ID'],
                'LAST_DEC_LV' => $item['LAST_DEC_LV'] ?? '',
                'LAST_EMP_LV' => $this->_nowLastEmpLv($item['USER_ID']),
                'LAST_STATUS' => $item['LAST_STATUS'] ?? 0,
                'LAST_CROWN_LV' => $item['LAST_CROWN_LV'] ?? '',
                'AMOUNT' => 0,
                'POINT' => $bonusPoint,
                'PERIOD_NUM' => $this->_periodNum,
                'CALC_YEAR' => $this->_calcYear,
                'CALC_MONTH' => $this->_calcYearMonth,
                'CREATED_AT' => Date::nowTime(),
                'PERF' => $monthTotalPV,
                'TRANSFER_RATE' => $mate,
                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
                'CAP_AMOUNT' => 0,
                'POINT_COMPLEX' => 0,
            ];

            $bonusPointComplex += $bonusPoint;
        }

        // 数据写入总表
        if ($insertBonusData) {
            // 计算个人奖金
            foreach ($insertBonusData as &$bonusData) {
                // 计算奖金
                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
                if ($amount <= 0) {
                    continue;
                }
                // 封顶前奖金数
                $capAmount = $amount;
                // 奖金数不能大于封顶值
                $amount = ($amount > $capBonus) ? $capBonus : $amount;

                $bonusData['AMOUNT'] = $amount;
                $bonusData['CAP_AMOUNT'] = $capAmount;
                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;

                // 放入缓存
                CalcCache::villaBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
            }

            CalcBonusVilla::batchInsert($insertBonusData);
        }

        return true;
    }

    // 执行车奖的计算
    public function calcBonusGarage() {
        // 月结，如果不是月结点，则直接退出
        if (!$this->_isCalcMonth) {
            return true;
        }

        $bonusConfig = $this->_sysConfig['openGarage'];
        // 达标条件：聘级、级别、奖项比例
        $config = json_decode($bonusConfig['OPTIONS'], true);
        // 奖金总比例
        $mate = $bonusConfig['VALUE'] / 100;
        // 会员级别
        $minDecLevel = $config['declarationLevel'] ?? [];
        // 个人奖金封顶
        $capBonus = intval($this->_sysConfig['openGarageCap']['VALUE'] ?? 0);

        // 月度公司总PV
        $monthTotalPV = PerfMonth::find()
            ->yearMonth($this->_calcYearMonth)
            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
            ->sum('PV_PCS');
        // 用于分发的奖金总数
        $transferAmount = $monthTotalPV * $mate;

        // 基于蓝星奖结果计算符合获奖条件的会员StarDirector
        $userStarDirector = CalcBonusBS::find()
            ->yearMonth($this->_calcYearMonth)
            ->where('CALC_MONTH = :CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
            ->select('USER_ID,LEVEL_ID,LAST_DEC_LV,LAST_STATUS')
            ->groupBy('USER_ID')
            ->asArray()
            ->all();
        $userStarDirectorObj = array_column($userStarDirector, NULL, 'USER_ID');

        // 基于团队奖/绩效奖结果计算会员的StarCrown.StarCrown基于周期计算,一个月会产生多次,取月周期中的最高星级
        $subQuery = CalcBonusQY::find()
            ->yearMonth($this->_calcYearMonth)
            ->where('CALC_MONTH = :CALC_MONTH AND LAST_CROWN_LV <> :NO_CROWN_LV', [':CALC_MONTH' => $this->_calcYearMonth, ':NO_CROWN_LV' => StarCrownLevel::NO_LEVEL_ID])
            ->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')
            ->joinWith(['starCrown' => function($query) {
                $query->select(['LEVEL_NAME', 'SORT']);
            }])
            ->having(1)
            ->orderBy('USER_ID ASC, SORT DESC');
        $userStarCrownObj = (new Query())->from(['u' => $subQuery])->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')->groupBy('USER_ID')->indexBy('USER_ID')->all();
        
        // 合并用户ID,去重
        $bonusUsers = array_unique(array_merge(array_keys($userStarDirectorObj), array_keys($userStarCrownObj)));
        sort($bonusUsers);

        // 奖金点数综合
        $bonusPointComplex = 0;
        $insertBonusData = [];
        foreach($bonusUsers as $userId) {
            // 计算奖金：取starDirectorPoint和starCrownPoint的大个值
            $starDirectorPoint = !isset($userStarDirectorObj[$userId]['LEVEL_ID']) ? 0 : ($this->_empLevelConfig[$userStarDirectorObj[$userId]['LEVEL_ID']]['GARAGE_PERCENT'] ?? 0);
            $starCrownPoint = !isset($userStarCrownObj[$userId]['LAST_CROWN_LV']) ? 0: ($this->_starCrownLevelConfig[$userStarCrownObj[$userId]['LAST_CROWN_LV']]['GARAGE_PERCENT'] ?? 0);
            // 奖金比例：
            $bonusPoint = max($starDirectorPoint, $starCrownPoint);
            if ($bonusPoint <= 0) {
                continue;
            }

            // 会员级别达到要求才会发放奖金
            $lastDecLv = $userStarDirectorObj[$userId]['LAST_DEC_LV'] ?? ($userStarCrownObj[$userId]['LAST_DEC_LV'] ?? '');
            if ($lastDecLv != $minDecLevel) {
                continue;
            }

            $insertBonusData[] = [
                'ID' => SnowFake::instance()->generateId(),
                'USER_ID' => $userId,
                'LAST_DEC_LV' => $userStarDirectorObj[$userId]['LAST_DEC_LV'] ?? ($userStarCrownObj[$userId]['LAST_DEC_LV'] ?? ''),
                'LAST_EMP_LV' => $userStarDirectorObj[$userId]['LEVEL_ID'] ?? '',
                'LAST_STATUS' => $userStarDirectorObj[$userId]['LAST_STATUS'] ?? ($userStarCrownObj[$userId]['LAST_STATUS'] ?? 1),
                'LAST_CROWN_LV' => $userStarCrownObj[$userId]['LAST_CROWN_LV'] ?? '',
                'AMOUNT' => 0,
                'POINT' => $bonusPoint,
                'PERIOD_NUM' => $this->_periodNum,
                'CALC_YEAR' => $this->_calcYear,
                'CALC_MONTH' => $this->_calcYearMonth,
                'CREATED_AT' => Date::nowTime(),
                'PERF' => $monthTotalPV,
                'TRANSFER_RATE' => $mate,
                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
                'CAP_AMOUNT' => 0,
                'POINT_COMPLEX' => 0,
            ];

            $bonusPointComplex += $bonusPoint;
        }

        // 数据写入总表
        if ($insertBonusData) {
            foreach ($insertBonusData as &$bonusData) {
                // 计算奖金
                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
                if ($amount <= 0) {
                    continue;
                }
                // 封顶前奖金数
                $capAmount = $amount;
                // 奖金数不能大于封顶值
                $amount = ($amount > $capBonus) ? $capBonus : $amount;

                $bonusData['AMOUNT'] = $amount;
                $bonusData['CAP_AMOUNT'] = $capAmount;
                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;

                // 放入缓存
                CalcCache::garageBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
            }

            CalcBonusGarage::batchInsert($insertBonusData);
        }

        return true;
    }

    /**
     * 季度奖写用户缓存
     */
    public function calcQuarterUser(int $offset = 0) {
        if( !$this->_isCalcMonth || !in_array($this->_calcMonth, [3,6,9,12])) {
            // 不是结算月,则不进行计算
            return false;
        }
        try{
            $allData = CalcBonusQuarter::finduseDbCalc()
                    ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
                    ->groupBy('USER_ID')
                    ->offset($offset)
                    ->limit($this->_limit)
                    ->asArray()
                    ->all();
            if ($allData){
                // 达标条件：会员级别：钻卡
                $config = json_decode($this->_sysConfig['openQuarter']['OPTIONS'], true);
                $minDecLevel = $config['declarationLevel'] ?? [];
                foreach ($allData as $user) {
                    // 管理奖钻卡发放
                    if ($user['LAST_DEC_LV'] == $minDecLevel) {
                        CalcCache::bonus($user['USER_ID'], $this->_periodNum, 'BONUS_QUARTER', $user['ORI_BONUS']);
                    } else {
                        // 如果未发奖,则将实发字段,改成0
                        CalcBonusQuarter::updateAll(
                            [
                                'AMOUNT' => '0.00'
                            ],
                            'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM',
                            [
                                ':USER_ID' => $user['USER_ID'], ':PERIOD_NUM' => $this->_periodNum
                            ]
                        );
                    }
                }
                return $this->calcQuarterUser($offset + $this->_limit);
            }
        } catch(Exception $e) {
            var_dump($e->getMessage(),'--------------------');
        }
        unset($allData);
        return true;
    }

    /**
     * 蓝星管理奖金未拆分
     */
    public function calcBonusBsGL(int $offset = 0) {
        if( !$this->_isCalcMonth ) {
            // 不是结算月,则不进行计算
            return false;
        }
        // 从缓存获取分页有收入的会员信息
        $allData = CalcBonusBS::findUseDbCalc()
            ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
            ->groupBy('USER_ID')
            ->offset($offset)
            ->limit($this->_limit)
            ->asArray()
            ->all();
        if ($allData) {
            foreach ($allData as $user) {
                // 是否活跃会员
                $isActive = User::isPerfActive($user['USER_ID'], $this->_periodNum, $this->_calcYearMonth);
                $oriBonus = $isActive ? $user['ORI_BONUS'] : 0;
                $lastEmpLv = $isActive ? $user['LEVEL_ID'] : EmployLevel::getDefaultLevelId();
                if ($oriBonus > 0) {
                    CalcCache::bonus($user['USER_ID'], $this->_periodNum, 'BONUS_BS', $user['ORI_BONUS']);
                }
                // 如果不活跃,则不发放奖金,积分,级别
                // 更新蓝星奖金存储过程的实发金额数据
                CalcBonusBS::updateAll(
                    [
                        'IS_ACTIVE' => (int)$isActive,
                        'HOPE_EMP_LV' => $user['LEVEL_ID'],
                        'LEVEL_ID' => $lastEmpLv,
                        'HOPE_BONUS' => $user['ORI_BONUS'],
                        'ORI_BONUS' => $oriBonus,
                        'HOPE_BONUS_MNT' => $user['ORI_BONUS_MNT'],
                        'HOPE_BONUS_ABBR' => $user['ORI_BONUS_ABBR'],
                        'ORI_BONUS_MNT' => $isActive ? $user['ORI_BONUS_MNT'] : 0,
                        'ORI_BONUS_ABBR' => $isActive ? $user['ORI_BONUS_ABBR'] : 0,
                    ],
                'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', 
                [':USER_ID' => $user['USER_ID'], ':PERIOD_NUM' => $this->_periodNum]);
                // 修改用户缓存中记录的用户的最新级别
                // $userInfo = CalcCache::getUserInfo($user['USER_ID'], $this->_periodNum);
                // $userInfo['LAST_EMP_LV'] = $lastEmpLv;
                // CalcCache::setUserInfo($user['USER_ID'], $this->_periodNum, $userInfo);
            }
            return $this->calcBonusBsGL($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }

    /**
     * 对碰
     */
    public function touchPerf(array $oriPerfArr, array $perfArr, $percent, $loopTimes=1) {
        $resultArr = $oriPerfArr;
        foreach ($perfArr as $keyT => $perfT) {
            if ($perfT <= 0) {
                unset($perfArr[$keyT]);
            }
        }
        if (count($perfArr) >= 2 && $loopTimes < 6) {
            arsort($perfArr, SORT_NUMERIC);
            $onePerf = null;
            $oneKey = null;
            // $touchSurplusAmount = 0;
            $touchAmount = 0;
            // 前两个进行对碰
            foreach ($perfArr as $key => $perf) {
                if ($onePerf === null) {
                    $oneKey = $key;
                    $onePerf = $perf;
                } else {
                    $touchSurplusAmount = $perf - $onePerf;
                    if ($touchSurplusAmount > 0) {
                        unset($perfArr[$oneKey]);
                        $resultArr['perfArr'][$oneKey] = 0;
                        $perfArr[$key] = $touchSurplusAmount;
                        $touchAmount = $onePerf;
                    } elseif ($touchSurplusAmount < 0) {
                        unset($perfArr[$key]);
                        $resultArr['perfArr'][$key] = 0;
                        $perfArr[$oneKey] = abs($touchSurplusAmount);
                        $touchAmount = $perf;
                    } else {
                        unset($perfArr[$oneKey], $perfArr[$key]);
                        $resultArr['perfArr'][$oneKey] = 0;
                        $resultArr['perfArr'][$key] = 0;
                        $touchAmount = $perf;
                    }
                    break;
                }
            }
            $touchBonus = Tool::formatPrice($touchAmount * $percent);
            $resultArr['touchBonus'] += $touchBonus;

            foreach ($perfArr as $keyR => $perfR) {
                $resultArr['perfArr'][$keyR] = $perfR;
            }
            return $this->touchPerf($resultArr, $perfArr, $percent, $loopTimes+1);

        }
        return $resultArr;
    }

    /**
     * 循环父级并执行回调函数
     * @param $userId
     * @param callable $callbackFunc
     * @param int $offset
     * @return bool
     */
    public function loopNetworkParentDo($userId, callable $callbackFunc, int $offset = 0) {
        $allParents = Cache::getAllNetworkParents($userId);
        $allData = array_slice($allParents, $offset, $this->_limit);
        unset($allParents);
        if ($allData) {
            foreach ($allData as $data) {
                $funcResult = $callbackFunc($data);
                if ($funcResult === self::LOOP_FINISH) {
                    return true;
                } elseif ($funcResult === self::LOOP_CONTINUE) {
                    continue;
                }
                unset($data, $funcResult);
            }
            unset($allData);
            return $this->loopNetworkParentDo($userId, $callbackFunc, $offset + $this->_limit);
        }
        return true;
    }

    /**
     * 循环推荐网络的父级
     * @param $userId
     * @param callable $callbackFunc
     * @param int $offset
     * @return bool
     */
    public function loopRelationParentDo($userId, callable $callbackFunc, int $offset = 0) {
        $allParents = Cache::getAllRelationParents($userId);
        $allData = array_slice($allParents, $offset, $this->_limit);
        unset($allParents);
        if ($allData) {
            foreach ($allData as $data) {
                $funcResult = $callbackFunc($data);
                if ($funcResult === self::LOOP_FINISH) {
                    return true;
                } elseif ($funcResult === self::LOOP_CONTINUE) {
                    continue;
                }
                unset($data, $funcResult);
            }

            unset($allData);
            return $this->loopRelationParentDo($userId, $callbackFunc, $offset + $this->_limit);
        }
        return true;
    }

    public function loopRelationDecDo($userId, callable $callbackFunc) {
        // 查询会员产生时，填写的报单中心
        return DecOrder::find()->where('TO_USER_ID=:TO_USER_ID', [':TO_USER_ID' => $userId])->select('DEC_ID')->orderBy('CREATED_AT DESC')->scalar();
    }


    /**
     * 按级别的收入上限
     * 新的需求调整: 改成不按月进行限制,并且去掉vip奖
     * @param $bonus
     * @param $userId
     * @param $declarationLevel
     * @return float
     */
    public function declarationLevelCap($bonus, $userId, $declarationLevel) {
        $decLevelConfig = $this->_decLevelConfig;
        $nowDecLevelConfig = $decLevelConfig[$declarationLevel];
        unset($decLevelConfig);
        $maxGetBonus = $nowDecLevelConfig['INCOME_CAP'];
        if( $bonus <= $maxGetBonus) {
            return $bonus;
        }else {
            return $maxGetBonus;
        }
    }

    /**
     * 扣除管理费 返回管理费
     */
    public function deductManageTax($bonus) {
        return $bonus * $this->_sysConfig['manageTaxPercent']['VALUE'] / 100;
    }

    // 扣除复消积分 返回应扣除的复消费积分
    public function deductReconsumePonits($userId, $bonus) {
        //判断是否达到了本月扣除复消的上限
        $cacheData = CalcCache::monthLastPeriodReconsumePoints($userId, $this->_periodNum, $this->_calcYearMonth);
        $bonusCache = CalcCache::bonus($userId, $this->_periodNum);
        $lastPeriodPoints = '0.00';
        if (!empty($cacheData)) {
            $lastPeriodPoints =  $cacheData['RECONSUME_POINTS_SUM'];
        }
        $reConsumePointsTotal = $bonusCache['RECONSUME_POINTS'] + $lastPeriodPoints;
        $reConsumePointsCap = $this->_sysConfig['reConsumePointsMonthCap']['VALUE'];
        unset($cacheData, $bonusCache, $lastPeriodPoints);
        $reConsumePoints = 0;
        if( $reConsumePointsTotal < $reConsumePointsCap ) {
            $reConsumePoints = $bonus * $this->_sysConfig['reConsumePointsPercent']['VALUE'] / 100;

            $reConsumePoints = min($reConsumePoints, $reConsumePointsCap-$reConsumePointsTotal);
        }
        unset($reConsumePointsTotal, $reConsumePointsCap);

        return $reConsumePoints;
    }

    /**
     * 更新百分比并发送
     * @param $percent
     */
    private function _updatePercent($percent) {
        // 把数据写入数据库中
        Period::updateAll(['CALC_PERCENT' => $percent], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin($percent, ['MODEL' => 'PERIOD', 'ID' => $this->_periodId, 'FIELD' => 'CALC_PERCENT']);
    }

    /**
     * 循环奖服务奖金会员，并入库
     */
    public function loopBonusUsers($offset = 0) {
        echo sprintf("时间:[%s]缓存奖金数据入库，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        // 从缓存列表里面从底层往上倒序获取会员
        $allData = CalcCache::getHasBonusUsers($this->_periodNum, $offset, $this->_limit);
        
            if($allData){
                $insertDataBonus = [];
                foreach($allData as $userId){
                    try {
                    $tempBonusData = $this->bonusData($userId);
                    if(!empty($tempBonusData['result'])){
                        $insertDataBonus[] = $tempBonusData['result'];
                    }
                    }catch(Exception $e) {
                        var_dump($e->getMessage(),'ssssssssssssssss',$userId);
                    }
                    unset($userId, $tempBonusData);
                }
                CalcBonus::batchInsert($insertDataBonus);

                unset($insertDataBonus, $allData);
                return $this->loopBonusUsers($offset + $this->_limit);
            }
         
        return true;
    }

    /**
     * 奖金入库
     */
    public function bonusData($userId) {
        // 车奖和房奖是积分,不参加到奖金累计中.季度奖季度发放,无最低消费限制
        try{
            $bonus = CalcCache::bonus($userId, $this->_periodNum);
            $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
            $perfData = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
            $tourismBonus = CalcCache::tourismBonus($userId, $this->_periodNum);
            $garageBonus = CalcCache::garageBonus($userId, $this->_periodNum);
            $villaBonus = CalcCache::villaBonus($userId, $this->_periodNum);
            $pervSurplusPerf = CalcCache::surplusPerf($userId, $this->_periodNum);
            $starCrownLv = CalcCache::getUserStarCrown($userId, $this->_periodNum);// 星级
            $bonusReal = '0.000'; // 总实发   
            $deductManageTaxItems = Config::DEDUCT_MANAGE_TAX; // 扣除管理费的列表  
            $deductReconsumePointsItems = Config::DEDUCT_RECONSUME_POINTS;// 扣除复消积分的列表
            $totalReconsumePointSum = $totalManageSum = '0.000'; // 需要扣除的奖金项,扣除完之后的实发金额
            $deductManageTax = '0.000'; //  扣除的管理费
            $deductReconsumePoints = '0.000'; //  扣除的复消积分 
            foreach($deductManageTaxItems as $v) {
                $totalManageSum+= $bonus[$v]; // 需要扣除管理费的累计之和
            }
            foreach($deductReconsumePointsItems as $v) {
                $totalReconsumePointSum+= $bonus[$v]; // 需要扣除复消积分的累计之和 
            }

            // 扣除管理费
            if ($totalManageSum > 0) {
                $deductManageTax = $this->deductManageTax($totalManageSum);
            }
            // 扣除复消积分
            if ($totalReconsumePointSum > 0) {
                $deductReconsumePoints = $this->deductReconsumePonits($userId, $totalReconsumePointSum);
            }
            // 总实发=总原奖金-扣除的总管理费-总复消积分
            $bonusReal = $bonus['BONUS_TOTAL'] - $deductManageTax - $deductReconsumePoints;
            // NG系统,不发放兑换积分
            // 因为是最后统一发奖金,每个奖金得实发是和原奖金一样得.只不过要扣除管理费和复消积分.
            // 当然,ng现在不扣除管理费和复消积分.所以都是0
            $result = [
                'USER_ID' => $userId,
                'LAST_USER_NAME' => $baseInfo['USER_NAME'],
                'LAST_REAL_NAME' => $baseInfo['REAL_NAME'],
                'LAST_DEC_LV' => $baseInfo['DEC_LV'],
                'LAST_EMP_LV' => $this->_nowLastEmpLv($userId),
                'LAST_CROWN_LV' => $starCrownLv ?? StarCrownLevel::getDefaultLevelId(),
                'RECONSUME_POINTS' => $deductReconsumePoints,
                'MANAGE_TAX' => $deductManageTax,    // 管理费
                'BONUS_REAL'=>  $bonusReal,
                'BONUS_TOTAL'=>$bonus['BONUS_TOTAL'],
                'ORI_BONUS_BD' => $bonus['ORI_BONUS_BD'],
                'ORI_BONUS_TG' => $bonus['ORI_BONUS_TG'],
                'ORI_BONUS_BS' => $bonus['ORI_BONUS_BS'], // 蓝星管理奖金原奖金
                'ORI_BONUS_QY' => $bonus['ORI_BONUS_QY'],
                'ORI_CAPPED_BONUS_QY' => $bonus['ORI_CAPPED_BONUS_QY'], // 团队奖封顶前的奖金
                'ORI_BONUS_QUARTER' => $bonus['ORI_BONUS_QUARTER'],
                'ORI_BONUS_TOURISM' => $tourismBonus, // 旅游奖
                'ORI_BONUS_VILLA' => $villaBonus, // 房奖
                'ORI_BONUS_GARAGE' => $garageBonus, // 车奖
                'PV_1L' => $perfData['PV_1L_TOUCH'],//TOUCH为碰业绩
                'QY_1L' => $perfData['PV_1L_TOUCH'] + $pervSurplusPerf['SURPLUS_1L'],
                'SURPLUS_1L' => $perfData['SURPLUS_1L'],
                'PV_2L' => $perfData['PV_2L_TOUCH'],
                'QY_2L' => $perfData['PV_2L_TOUCH'] + $pervSurplusPerf['SURPLUS_2L'],
                'SURPLUS_2L' => $perfData['SURPLUS_2L'],
                'PV_3L' => $perfData['PV_3L_TOUCH'],
                'QY_3L' => $perfData['PV_3L_TOUCH'] + $pervSurplusPerf['SURPLUS_3L'],
                'SURPLUS_3L' => $perfData['SURPLUS_3L'],
                'PV_4L' => $perfData['PV_4L_TOUCH'],
                'QY_4L' => $perfData['PV_4L_TOUCH'] + $pervSurplusPerf['SURPLUS_4L'],
                'SURPLUS_4L' => $perfData['SURPLUS_4L'],
                'PV_5L' => $perfData['PV_5L_TOUCH'],
                'QY_5L' => $perfData['PV_5L_TOUCH'] + $pervSurplusPerf['SURPLUS_5L'],
                'SURPLUS_5L' => $perfData['SURPLUS_5L'],
                'PV_PCS' => $perfData['PV_PCS'],
                'PV_TOUCH' => Tool::formatPrice($perfData['PV_1L_TOUCH'] + $perfData['PV_2L_TOUCH'] + $perfData['PV_3L_TOUCH'] + $perfData['PV_4L_TOUCH'] + $perfData['PV_5L_TOUCH']),
                'PERIOD_NUM' => $this->_periodNum,
                'CALC_YEAR' => $this->_calcYear,
                'CALC_MONTH' => $this->_calcYearMonth,
                'CREATED_AT' => Date::nowTime(),
            ];
            
        } catch(Exception $e) {
            var_dump($e->getMessage(),'-----------------------------------------------',$userId);
        }
        $resend = [];
        unset($bonus, $bonusReal);
        return ['result'=>$result,'resend'=>$resend];
    }

    // 判断是否满足月最低消费
    public function _isMonthPerfLimit($userId) {
        $userMonthTotal = PerfMonth::find()->where(
            'USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', 
            ['USER_ID'=>$userId, 'CALC_MONTH'=>$this->_calcYearMonth]
        )
        ->asArray()
        ->one();
        $fxPvStatus = false;
        if (isset($userMonthTotal['PV_PCS']) && $userMonthTotal['PV_PCS'] >= $this->_sysConfig['monthPcsPvFxCondition']['VALUE']) {
            $fxPvStatus = true;
        }
        
        return $fxPvStatus;
    }

    // 获取用户当期最新聘级
    // 非结算月,直接获取传过来的用户级别.如果是结算月,bs流水表无数据则是无级别.有数据,则直接取缓存中数据.因为第一步计算bs奖已更新缓存数据
    public function _nowLastEmpLv($userId) {
        // 如果是结算月,且蓝星奖流水没记录,则为无级别
        $lastEmpLv = EmployLevel::getDefaultLevelId();
        $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
        if( $this->_isCalcMonth ) {
            $hasBs = CalcBonusBS::findUseDbCalc()
            ->where('PERIOD_NUM=:PERIOD_NUM AND USER_ID=:USER_ID', [':PERIOD_NUM' => $this->_periodNum, ':USER_ID' => $userId])
            ->asArray()
            ->one();
            if (!empty($hasBs)) {
                $lastEmpLv = $hasBs['LEVEL_ID'];
            }
        } else {
            $lastEmpLv = $userBaseInfo['LAST_EMP_LV'];
        }

        return $lastEmpLv;
    }
}