<?php
/**
 * Created by PhpStorm.
 * User: liyunlong
 * Date: 2019-01-11
 * Time: 15:27
 */

namespace common\helpers\bonus;


use common\helpers\Cache;
use common\helpers\Date;
use common\helpers\Form;
use common\helpers\snowflake\SnowFake;
use common\helpers\Tool;
use common\helpers\user\Info;
use common\helpers\user\Reconsume;
use common\models\CalcBonus;
use common\models\DeclarationLevel;
use common\models\forms\DeclarationForm;
use common\models\Order;
use common\models\OrderDec;
use common\models\OrderShop;
use common\models\OrderStandard;
use common\models\PerfCompany;
use common\models\PerfMonth;
use common\models\PerfOrder;
use common\models\PerfPeriod;
use common\models\PerfStandard;
use common\models\Period;
use common\models\DecOrder;
use common\models\EmployLevel;
use common\models\UserRelation;
use yii\base\Exception;
use yii\base\StaticInstanceTrait;

class PerfCalc {
    use StaticInstanceTrait;

    private $_limit = 1000;
    private $_handleUserId;
    private $_companyMonthPerf = 0;
    private $_cfTotalPercent = 0;
    private $_lxTotalPercent = 0;
    private $_sysConfig = [];
    private $_decLevelConfig = [];
    private $_empLevelConfig = [];
    private $_decRoleConfig = [];
    private $_errors = [];
    private $_periodNum = 0;
    private $_periodId;
    private $_isCalcMonth = 0;
    private $_calcYear;
    private $_calcMonth;
    private $_calcYearMonth;
    private $_lastCalcYear;
    private $_lastCalcMonth;
    private $_lastCalcYearMonth;
    private $_lastPeriodNum;
    private $_lastPeriodYear;
    private $_lastPeriodMonth;
    private $_lastPeriodYearMonth;
    //pv
    private $_pvRatio;

    const LOOP_FINISH = 1;
    const LOOP_CONTINUE = 2;

    const ORDER_PAY_TYPE_CASH = 'cash';
//    const ORDER_PAY_TYPE_POINT = 'point';

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

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

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

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

    /**
     * 计算步骤
     * @param $periodNum
     * @param null $handleUserId
     * @return bool
     */
    public function calcStep($periodNum, $handleUserId = null) {
        try {
            $this->_errors = [];
            $this->setPeriodNum($periodNum);
            $this->_handleUserId = $handleUserId;
            $t1 = microtime(true);
            // 初始化结算任务
            $this->initCalcTask();
            // 设置结算状态
            $this->setCalcStatus('start');
            // 清空所有本期结算用到的缓存
            CalcCache::clearAll($this->_periodNum);
            // 清空会员推荐和接点关系缓存
            CalcCache::clearNetCache();
            // 清空相关表数据
            $this->clearTableData();
            $t2 = microtime(true);
            echo('初始化、清空缓存及相关数据表完成，耗时：' . round($t2 - $t1, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(10);
            // 计算月奖，才需要向缓存中加入按推荐深度的所有用户
            //修改每一期都缓存所有用户
            CalcCache::addUsers($this->_periodNum);
            $t3 = microtime(true);
            echo('向缓存中加入用户完成，耗时：' . round($t3 - $t2, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(20);
            // 周结，循环向上级计入业绩并加入业绩单
            $this->loopCalcPeriodPerfByDecOrder();
            $this->loopCalcPeriodPerfByOrderDec();
            $t4 = microtime(true);
            echo('计算周业绩表中的数据完成，耗时：' . round($t4 - $t1, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(40);
            // 从会员的复销订单会员计算复销业绩并加入业绩单
            $this->loopCalcPerfByFXOrder();
            $this->loopCalcPerfByShopFXOrder();
            $t5 = microtime(true);
            echo('计算复销业绩并写入业绩单完成，耗时：' . round($t5 - $t4, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(60);
            //本期业绩入库
            $this->loopWriteNowPerf();
            $t6 = microtime(true);
            echo('本期业绩入库完成，耗时：' . round($t6 - $t5, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(70);

            //计算月业绩表中的数据
            $this->loopCalcMonthPerfTableData();
            $t7 = microtime(true);
            echo('计算月业绩表中的数据完成，耗时：' . round($t7 - $t6, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(80);

            //每月计算聘级
            $this->loopCalcEmpLevel();
            $t8 = microtime(true);
            echo('计算聘级完成，耗时：' . round($t8 - $t7, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(90);

            //本月业绩入库
            $this->loopWriteMonthPerf();
            $t7 = microtime(true);
            echo('本月业绩入库完成，耗时：' . round($t7 - $t6, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(95);

            //达标业绩
            $this->loopCalcPerfByStandardFXOrder();
            //达标业绩入库
            $this->loopWriteStandardPerf();
            $t8 = microtime(true);
            echo('本月业绩入库完成，耗时：' . round($t8 - $t7, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
            $this->_updatePercent(100);


            echo('结算全部完成，共耗时：' . round($t8 - $t1, 3) . ',内存使用：' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
        } catch (\Exception $e) {
            $this->errorCalcTask();
            $this->addError('calc', $e->getMessage());
            return false;
        }
        return true;
    }

    /**
     * 结算完成
     * @return bool
     */
    public function endCalcTask() {
        // 更新结算状态
        $this->setCalcStatus('end');
        //如果自动结算
        if(boolval($this->_sysConfig['autoCalcPeriod']['VALUE'])){
            $period = Period::instance();
            if($period->isLastSent($this->_periodNum)) {
                $bonusCalc = BonusCalc::instance();
                $asyncResult = $bonusCalc->calcStep($this->_periodNum);
                if ($asyncResult) {
                    $bonusCalc->endCalcTask();
                } else {
                    $bonusCalc->errorCalcTask();
                }
                return $asyncResult;
            }
        }
    }

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

    /**
     * 设置生成业绩单状态
     * @param $type
     * start|end|fail
     */
    public function setCalcStatus($type) {
        if ($type == 'start') {
            Period::updateAll(['IS_PERFING' => 1, 'IS_PERFED' => Period::PERF_NONE, 'PERF_STARTED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
        } elseif ($type == 'end') {
            Period::updateAll(['IS_PERFING' => 0, 'IS_PERFED' => Period::PERF_FINISH, 'PERFED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
        } elseif ($type == 'fail') {
            Period::updateAll(['IS_PERFING' => 0, 'IS_PERFED' => Period::PERF_FAIL, 'PERFED_AT' => 0], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
        }
    }

    /**
     * 初始化结算任务
     * @throws \yii\db\Exception
     */
    public function initCalcTask() {
        $this->_sysConfig = Cache::getSystemConfig();
        $this->_decLevelConfig = Cache::getDecLevelConfig();
        $this->_empLevelConfig = Cache::getEmpLevelConfig();
        $this->_decRoleConfig = CalcCache::getDecRoleConfig($this->_periodNum);
        $periodNum = $this->_periodNum;
        // 获取本年月和上年月
        $periodObj = Period::instance();
        $periodDataArr = $periodObj->setPeriodNum($periodNum);
        $this->_periodId = $periodDataArr['ID'];
        $this->_isCalcMonth = $periodObj->isCalcMonth($periodNum);
        $this->_calcYear = $periodObj->getYear($periodNum);
        $this->_calcMonth = $periodObj->getMonth($periodNum);
        $this->_calcYearMonth = $periodObj->getYearMonth($periodNum);
        $lastYearMonthArr = $periodObj->getLastMonth($periodNum);
        $this->_lastCalcYear = $lastYearMonthArr['year'];
        $this->_lastCalcMonth = $lastYearMonthArr['month'];
        $this->_lastCalcYearMonth = $lastYearMonthArr['yearMonth'];
        $this->_lastPeriodNum = $periodNum - 1;
        if (Period::isExistsPeriodNum($this->_lastPeriodNum)) {
            $this->_lastPeriodYear = $periodObj->getYear($this->_lastPeriodNum);
            $this->_lastPeriodMonth = $periodObj->getMonth($this->_lastPeriodNum);
            $this->_lastPeriodYearMonth = $periodObj->getYearMonth($this->_lastPeriodNum);
        } else {
            $this->_lastPeriodYear = 0;
            $this->_lastPeriodMonth = 0;
            $this->_lastPeriodYearMonth = 0;
        }
        $this->_pvRatio = $this->_sysConfig['pvRatio']['VALUE'];
    }

    /**
     * 清空相关表数据
     */
    public function clearTableData() {
        // 周业绩
        PerfPeriod::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
        // 业绩单
        PerfOrder::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
        // 月结时要清空的数据
        if ($this->_isCalcMonth) {
            // 月业绩表
            PerfMonth::pageDeleteAll("CALC_MONTH='{$this->_calcYearMonth}'");
            //达标业绩表
            PerfStandard::pageDeleteAll("CALC_MONTH='{$this->_calcYearMonth}'");
        }
    }

    /**
     * 周结，向上级算业绩，并计入业绩单
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function loopCalcPeriodPerfByDecOrder($offset = 0) {
        // 循环获取全部报单
        $allData = DecOrder::findUseDbCalc()->select('ID,DEC_SN,ORDER_SN,USER_ID,TYPE,TO_USER_ID,IS_ADMIN,DEC_AMOUNT,DEC_PV,PERIOD_NUM,CALC_MONTH,IS_DEL,P_CALC_MONTH,CREATED_AT,DEC_ID')->where("PERIOD_NUM=:PERIOD_NUM AND IS_DEL=0 AND TYPE='ZC'", [':PERIOD_NUM' => $this->_periodNum])->orderBy('CREATED_AT DESC,ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
        if ($allData) {
            $insertPerfOrderData = [];
            foreach ($allData as $data) {
                // 是否关停等状态不能拿业绩
                if (!$this->isHasPerf($data['TO_USER_ID'])) {
                    continue;
                }
                //零售单不累计业绩，仅存业绩单
//                if($data['TYPE']!='LS') {
                    // 给自己增加PCS(个人消费)
                CalcCache::nowPeriodPerf($data['TO_USER_ID'], $this->_periodNum, [
                    'PV_PCS' => $data['DEC_PV'],
                    'PV_PCS_ZC' => $data['DEC_PV'],
                ]);
                // 把该会员加入到能拿到业绩的会员缓存中
                CalcCache::addHasPerfUsers($data['TO_USER_ID'], $this->_periodNum);
                //加入到报单会员中
                $toInfo = CalcCache::getUserInfo($data['TO_USER_ID'], $this->_periodNum);
                CalcCache::addHasBDUsers($data['TO_USER_ID'], $this->_periodNum, [
                    'TO_USER_ID' => $data['TO_USER_ID'],
                    'USER_ID' => $data['USER_ID'],
                    'DEC_ID' => $data['DEC_ID'],
                    //考虑可能会移网的情况
                    'REC_USER_ID' => $toInfo['REC_UID'] ?? '',
                    'CON_USER_ID' => $toInfo['CON_UID'] ?? '',
                    'DEC_AMOUNT' => $data['DEC_AMOUNT'],
                    'DEC_PV' => $data['DEC_PV'],
                ]);
                // 给上追加业绩
                $this->loopNetworkParentDo($data['TO_USER_ID'], function ($parent) use (&$data) {
                    // 给上级会员追加业绩到缓存中
//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
//                    ]);
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'PV_' . $parent['LOCATION'] . 'L' => $data['DEC_PV'],
                        'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $data['DEC_PV'],
                        'PV_' . $parent['LOCATION'] . 'L_' . $data['TYPE'] => $data['DEC_PV'],
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
                    unset($parent);
                });
                //给推荐关系累计增加业绩
                $this->loopRelationParentDo($data['TO_USER_ID'], function ($parent) use (&$data) {
                    // 给上级会员追加业绩到缓存中
//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
//                    ]);
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'PV_PSS' => $data['DEC_PV'],
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
                    unset($parent);
                });

//                }
                // 写入业绩单表
                $decInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
                $sn = PerfOrder::generateSN();
                $insertPerfOrderData[] = [
                    'ID' => SnowFake::instance()->generateId(),
                    'SN' => $sn,
                    'DEC_SN' => $data['DEC_SN'],
                    'DEC_TYPE' => strtoupper($data['TYPE']),
                    'DEC_STATUS' => PerfOrder::STATUS_NORMAL,
                    'USER_ID' => $data['TO_USER_ID'],
                    'LAST_REC_USER_NAME' => $toInfo['REC_USER_NAME'],
                    'LAST_REC_REAL_NAME' => $toInfo['REC_REAL_NAME'],
                    'LAST_DEC_LV' => $toInfo['DEC_LV'],
                    'LAST_EMP_LV' => $toInfo['EMP_LV'],
                    'LAST_STATUS' => $toInfo['STATUS'],
                    'PV' => $data['DEC_PV'],
                    'DEC_AMOUNT' => $data['DEC_AMOUNT'],
                    'LAST_SUB_COM_ID' => $toInfo['SUB_COM_ID'],
                    'LAST_PROVINCE' => $toInfo['PROVINCE'],
                    'LAST_CITY' => $toInfo['CITY'],
                    'LAST_COUNTY' => $toInfo['COUNTY'],
                    'DEC_USER_ID' => $data['USER_ID'],
                    'LAST_DEC_DEC_LV' => $decInfo['DEC_LV'],
                    'LAST_DEC_SUB_COM_ID' => $decInfo['SUB_COM_ID'],
                    'LAST_DEC_PROVINCE' => $decInfo['DEC_PROVINCE'],
                    'LAST_DEC_CITY' => $decInfo['DEC_CITY'],
                    'LAST_DEC_COUNTY' => $decInfo['DEC_COUNTY'],
                    'PERIOD_NUM' => $this->_periodNum,
                    'CALC_MONTH' => $this->_calcYearMonth,
                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
                    'CREATED_AT' => Date::nowTime(),
                    'CLOSED_AT' => 0,
                ];
                unset($data, $decInfo, $sn, $toInfo);
            }
            PerfOrder::batchInsert($insertPerfOrderData);
            unset($insertPerfOrderData, $allData, $snArr);
            return $this->loopCalcPeriodPerfByDecOrder($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }

    /**
     * 周结，向上级算业绩，并计入业绩单
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function loopCalcPeriodPerfByOrderDec($offset = 0) {
        // 循环获取全部报单
        $allData = OrderDec::findUseDbCalc()->select('ID,SN,USER_ID,ORDER_TYPE,ORDER_AMOUNT,PV,PAY_AMOUNT,PAY_PV,PERIOD_NUM,PAY_TYPE')->where("PERIOD_NUM=:PERIOD_NUM AND IS_DELETE=0 AND ORDER_TYPE=:ORDER_TYPE", [':PERIOD_NUM' => $this->_periodNum, ':ORDER_TYPE'=>'ZC'])->orderBy('ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
        if ($allData) {
            $insertPerfOrderData = [];
            foreach ($allData as $data) {
                // 是否关停等状态不能拿业绩
                if (!$this->isHasPerf($data['USER_ID'])) {
                    continue;
                }
                // 给自己增加PCS(个人消费)
                CalcCache::nowPeriodPerf($data['USER_ID'], $this->_periodNum, [
                    'PV_PCS' => $data['PAY_PV'],
                    'PV_PCS_ZC' => $data['PAY_PV'],
                ]);
                // 把该会员加入到能拿到业绩的会员缓存中
                CalcCache::addHasPerfUsers($data['USER_ID'], $this->_periodNum);
                //加入到报单会员中
                $toInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
                CalcCache::addHasBDUsers($data['USER_ID'], $this->_periodNum, [
                    'TO_USER_ID' => $data['USER_ID'],
                    'USER_ID' => $toInfo['DEC_ID'],
                    'DEC_ID' => $toInfo['DEC_ID'],
                    //考虑可能会移网的情况
                    'REC_USER_ID' => $toInfo['REC_UID'] ?? '',
                    'CON_USER_ID' => $toInfo['CON_UID'] ?? '',
                    'DEC_AMOUNT' => $data['ORDER_AMOUNT'],
                    'DEC_PV' => $data['PAY_PV'],
                ]);
                // 给上追加业绩
                $this->loopNetworkParentDo($data['USER_ID'], function ($parent) use (&$data) {
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'PV_' . $parent['LOCATION'] . 'L' => $data['PAY_PV'],
                        'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $data['PAY_PV'],
                        'PV_' . $parent['LOCATION'] . 'L_' . $data['ORDER_TYPE'] => $data['PAY_PV'],
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
                    unset($parent);
                });
                //给推荐关系累计增加业绩
                $this->loopRelationParentDo($data['USER_ID'], function ($parent) use (&$data) {
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'PV_PSS' => $data['PAY_PV'],
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
                    unset($parent);
                });

                // 写入业绩单表
                $decInfo = CalcCache::getUserInfo($toInfo['DEC_ID'], $this->_periodNum);
                $sn = PerfOrder::generateSN();
                $insertPerfOrderData[] = [
                    'ID' => SnowFake::instance()->generateId(),
                    'SN' => $sn,
                    'DEC_SN' => $data['SN'],
                    'DEC_TYPE' => strtoupper($data['ORDER_TYPE']),
                    'DEC_STATUS' => PerfOrder::STATUS_NORMAL,
                    'USER_ID' => $data['USER_ID'],
                    'LAST_REC_USER_NAME' => $toInfo['REC_USER_NAME'],
                    'LAST_REC_REAL_NAME' => $toInfo['REC_REAL_NAME'],
                    'LAST_DEC_LV' => $toInfo['DEC_LV'],
                    'LAST_EMP_LV' => $toInfo['EMP_LV'],
                    'LAST_STATUS' => $toInfo['STATUS'],
                    'PV' => $data['PAY_PV'],
                    'DEC_AMOUNT' => $data['ORDER_AMOUNT'],
                    'LAST_SUB_COM_ID' => $toInfo['SUB_COM_ID'],
                    'LAST_PROVINCE' => $toInfo['PROVINCE'],
                    'LAST_CITY' => $toInfo['CITY'],
                    'LAST_COUNTY' => $toInfo['COUNTY'],
                    'DEC_USER_ID' => $toInfo['DEC_ID'],
                    'LAST_DEC_DEC_LV' => $decInfo['DEC_LV'],
                    'LAST_DEC_SUB_COM_ID' => $decInfo['SUB_COM_ID'],
                    'LAST_DEC_PROVINCE' => $decInfo['DEC_PROVINCE'],
                    'LAST_DEC_CITY' => $decInfo['DEC_CITY'],
                    'LAST_DEC_COUNTY' => $decInfo['DEC_COUNTY'],
                    'PERIOD_NUM' => $this->_periodNum,
                    'CALC_MONTH' => $this->_calcYearMonth,
                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
                    'CREATED_AT' => Date::nowTime(),
                    'CLOSED_AT' => 0,
                ];
                unset($data, $decInfo, $sn, $toInfo);
            }
            PerfOrder::batchInsert($insertPerfOrderData);
            unset($insertPerfOrderData, $allData, $snArr);
            return $this->loopCalcPeriodPerfByOrderDec($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }


    /**
     * 从会员的复销订单会员计算复销业绩并加入业绩单
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function loopCalcPerfByFXOrder(int $offset = 0) {
        // 循环获取全部报单
        $allData = Order::findUseDbCalc()->select('ID,SN,DEC_SN,USER_ID,ORDER_TYPE,ORDER_AMOUNT,PAY_AMOUNT,PAY_PV,PAY_TYPE,PERIOD_NUM,STATUS,IS_DELETE,P_CALC_MONTH,CREATED_AT')->where("PERIOD_NUM=:PERIOD_NUM AND IS_DELETE=0 AND ORDER_TYPE=:ORDER_TYPE", [':PERIOD_NUM' => $this->_periodNum, ':ORDER_TYPE'=>DeclarationForm::TYPE_FX])->orderBy('CREATED_AT DESC,ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
        if ($allData) {
            $insertPerfOrderData = [];
            foreach ($allData as $data) {
                // 是否关停等状态不能拿业绩
                if (!$this->isHasPerf($data['USER_ID'])) {
                    continue;
                }

                //如果支付方式是现金，那么实际业绩是支付PV的50%
                if( $data['PAY_TYPE'] === self::ORDER_PAY_TYPE_CASH ) {
                    $orderCashAmount = $data['ORDER_AMOUNT'];
                    //111期开始由50%改为60%-by 2020-04-30修改
                    $payPv = $data['PAY_PV'] * $this->_sysConfig['cashReconsumeBonusPercent']['VALUE'] / 100;
                    $cacheDataKey = 'PV_PCS_FX_CASH';
                }else {
                    $orderCashAmount = 0;
                    $payPv = $data['PAY_PV'];
                    $cacheDataKey = 'PV_PCS_FX_POINT';
                }

                if( $payPv <= 0 ) continue;

                // 给自己增加PCS(个人消费)
                CalcCache::nowPeriodPerf($data['USER_ID'], $this->_periodNum, [
                    'FX_AMOUNT_CASH' => $orderCashAmount,
                    'PV_PCS' => $payPv,
                    'PV_PCS_FX' => $payPv,
                    $cacheDataKey => $payPv,
                ]);
                // 把该会员加入到能拿到业绩的会员缓存中
                CalcCache::addHasPerfUsers($data['USER_ID'], $this->_periodNum);
                // 给上追加业绩
                $this->loopNetworkParentDo($data['USER_ID'], function ($parent) use (&$data, $payPv) {
                    // 给上级会员追加业绩到缓存中
//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
//                    ]);
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'PV_' . $parent['LOCATION'] . 'L' => $payPv,
                        'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $payPv,
                        'PV_' . $parent['LOCATION'] . 'L_FX' => $payPv,
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
                });
                //给推荐关系累计增加业绩
                $this->loopRelationParentDo($data['USER_ID'], function ($parent) use ($data, $payPv) {
                    // 给上级会员追加业绩到缓存中
//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
//                    ]);
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'PV_PSS' => $payPv,
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
                });

//                }
                // 写入业绩单表
                $baseInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
                $sn = PerfOrder::generateSN();
                $insertPerfOrderData[] = [
                    'ID' => SnowFake::instance()->generateId(),
                    'SN' => $sn,
                    'DEC_SN' => null,
                    'DEC_TYPE' => 'FX',
                    'DEC_STATUS' => PerfOrder::STATUS_NORMAL,
                    'USER_ID' => $data['USER_ID'],
                    'LAST_REC_USER_NAME' => $baseInfo['REC_USER_NAME'],
                    'LAST_REC_REAL_NAME' => $baseInfo['REC_REAL_NAME'],
                    'LAST_DEC_LV' => $baseInfo['DEC_LV'],
                    'LAST_EMP_LV' => $baseInfo['EMP_LV'],
                    'LAST_STATUS' => $baseInfo['STATUS'],
                    'PV' => $payPv,
                    'DEC_AMOUNT' => $data['PAY_AMOUNT'],
                    'LAST_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
                    'LAST_PROVINCE' => $baseInfo['PROVINCE'],
                    'LAST_CITY' => $baseInfo['CITY'],
                    'LAST_COUNTY' => $baseInfo['COUNTY'],
                    'DEC_USER_ID' => $data['USER_ID'],
                    'LAST_DEC_DEC_LV' => $baseInfo['DEC_LV'],
                    'LAST_DEC_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
                    'LAST_DEC_PROVINCE' => $baseInfo['PROVINCE'],
                    'LAST_DEC_CITY' => $baseInfo['CITY'],
                    'LAST_DEC_COUNTY' => $baseInfo['COUNTY'],
                    'PERIOD_NUM' => $this->_periodNum,
                    'CALC_MONTH' => $this->_calcYearMonth,
                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
                    'CREATED_AT' => Date::nowTime(),
                    'CLOSED_AT' => 0,
                ];
                unset($data, $baseInfo, $sn, $orderCashAmount, $payPv, $cacheDataKey);
            }
            PerfOrder::batchInsert($insertPerfOrderData);

            unset($insertPerfOrderData, $allData, $snArr);
            return $this->loopCalcPerfByFXOrder($offset + $this->_limit);
        }

        unset($allData);
        return true;
    }

    /**
     * 从会员的商城复销订单会员计算复销业绩并加入业绩单
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function loopCalcPerfByShopFXOrder(int $offset = 0) {
        // 循环获取全部报单
        $allData = OrderShop::findUseDbCalc()->select('ID,SN,DEC_SN,USER_ID,ORDER_TYPE,ORDER_AMOUNT,PAY_AMOUNT,PAY_PV,PAY_TYPE,PERIOD_NUM,STATUS,IS_DELETE,P_CALC_MONTH,CREATED_AT')->where("PERIOD_NUM=:PERIOD_NUM AND IS_DELETE=0 AND ORDER_TYPE=:ORDER_TYPE", [':PERIOD_NUM' => $this->_periodNum, ':ORDER_TYPE'=>DeclarationForm::TYPE_FX])->orderBy('CREATED_AT DESC,ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
        if ($allData) {
            $insertPerfOrderData = [];
            foreach ($allData as $data) {
                // 是否关停等状态不能拿业绩
                if (!$this->isHasPerf($data['USER_ID'])) {
                    continue;
                }

                //如果支付方式是现金，那么实际业绩是支付PV的50%
                if( $data['PAY_TYPE'] === self::ORDER_PAY_TYPE_CASH ) {
                    $orderCashAmount = $data['ORDER_AMOUNT'];
                    //111期开始由50%改为60%-by 2020-04-30修改
                    $payPv = $data['PAY_PV'] * $this->_sysConfig['cashReconsumeBonusPercent']['VALUE'] / 100;
                    $cacheDataKey = 'PV_PCS_FX_CASH';
                }else {
                    $orderCashAmount = 0;
                    $payPv = $data['PAY_PV'];
                    $cacheDataKey = 'PV_PCS_FX_POINT';
                }

                if( $payPv <= 0 ) continue;

                // 给自己增加PCS(个人消费)
                CalcCache::nowPeriodPerf($data['USER_ID'], $this->_periodNum, [
                    'FX_AMOUNT_CASH' => $orderCashAmount,
                    'PV_PCS' => $payPv,
                    'PV_PCS_FX' => $payPv,
                    $cacheDataKey => $payPv,
                ]);
                // 把该会员加入到能拿到业绩的会员缓存中
                CalcCache::addHasPerfUsers($data['USER_ID'], $this->_periodNum);
                // 给上追加业绩
                $this->loopNetworkParentDo($data['USER_ID'], function ($parent) use (&$data, $payPv) {
                    // 给上级会员追加业绩到缓存中
//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
//                    ]);
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'PV_' . $parent['LOCATION'] . 'L' => $payPv,
                        'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $payPv,
                        'PV_' . $parent['LOCATION'] . 'L_FX' => $payPv,
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
                });
                //给推荐关系累计增加业绩
                $this->loopRelationParentDo($data['USER_ID'], function ($parent) use ($data, $payPv) {
                    // 给上级会员追加业绩到缓存中
//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
//                    ]);
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'PV_PSS' => $payPv,
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
                });

//                }
                // 写入业绩单表
                $baseInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
                $sn = PerfOrder::generateSN();
                $insertPerfOrderData[] = [
                    'ID' => SnowFake::instance()->generateId(),
                    'SN' => $sn,
                    'DEC_SN' => null,
                    'DEC_TYPE' => 'FX',
                    'DEC_STATUS' => PerfOrder::STATUS_NORMAL,
                    'USER_ID' => $data['USER_ID'],
                    'LAST_REC_USER_NAME' => $baseInfo['REC_USER_NAME'],
                    'LAST_REC_REAL_NAME' => $baseInfo['REC_REAL_NAME'],
                    'LAST_DEC_LV' => $baseInfo['DEC_LV'],
                    'LAST_EMP_LV' => $baseInfo['EMP_LV'],
                    'LAST_STATUS' => $baseInfo['STATUS'],
                    'PV' => $payPv,
                    'DEC_AMOUNT' => $data['PAY_AMOUNT'],
                    'LAST_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
                    'LAST_PROVINCE' => $baseInfo['PROVINCE'],
                    'LAST_CITY' => $baseInfo['CITY'],
                    'LAST_COUNTY' => $baseInfo['COUNTY'],
                    'DEC_USER_ID' => $data['USER_ID'],
                    'LAST_DEC_DEC_LV' => $baseInfo['DEC_LV'],
                    'LAST_DEC_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
                    'LAST_DEC_PROVINCE' => $baseInfo['PROVINCE'],
                    'LAST_DEC_CITY' => $baseInfo['CITY'],
                    'LAST_DEC_COUNTY' => $baseInfo['COUNTY'],
                    'PERIOD_NUM' => $this->_periodNum,
                    'CALC_MONTH' => $this->_calcYearMonth,
                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
                    'CREATED_AT' => Date::nowTime(),
                    'CLOSED_AT' => 0,
                ];
                unset($data, $baseInfo, $sn, $orderCashAmount, $payPv, $cacheDataKey);
            }
            PerfOrder::batchInsert($insertPerfOrderData);

            unset($insertPerfOrderData, $allData, $snArr);
            return $this->loopCalcPerfByShopFXOrder($offset + $this->_limit);
        }

        unset($allData);
        return true;
    }

    /**
     * 达标复销订单
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function loopCalcPerfByStandardFXOrder(int $offset = 0) {
        if (!$this->_isCalcMonth) {
            return true;
        }
        $allData = OrderStandard::findUseDbCalc()->select('ID,SN,DEC_SN,USER_ID,ORDER_TYPE,ORDER_AMOUNT,PAY_AMOUNT,PAY_PV,PAY_TYPE,PERIOD_NUM,STATUS,IS_DELETE,CREATED_AT')->where("CALC_MONTH=:CALC_MONTH", [':CALC_MONTH' => $this->_calcYearMonth])->orderBy('ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
        if ($allData) {
            $insertPerfOrderData = [];
            foreach ($allData as $data) {
                // 是否关停等状态不能拿业绩
                if (!$this->isHasPerf($data['USER_ID'])) {
                    continue;
                }

                //如果支付方式是现金，那么实际业绩是支付PV的50%
                if( $data['PAY_TYPE'] === self::ORDER_PAY_TYPE_CASH ) {
                    $orderCashAmount = $data['ORDER_AMOUNT'];
//                    $payPv = $data['PAY_PV'] * $this->_sysConfig['cashReconsumeBonusPercent']['VALUE'] / 100;
                }else {
                    $orderCashAmount = 0;
//                    $payPv = $data['PAY_PV'];
                }

                if( $orderCashAmount <= 0 ) continue;



                // 给自己增加个人业绩
                CalcCache::nowStandardMonthPerf($data['USER_ID'], $this->_periodNum, [
                    'AMOUNT_PCS' => $orderCashAmount,
                ]);
                // 把该会员加入到能拿到业绩的会员缓存中
                CalcCache::addHasStandardMonthPerfUsers($data['USER_ID'], $this->_periodNum);

                //只有无聘级业绩才向上累加
                $baseInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
                if( $baseInfo['EMP_LV'] !=  EmployLevel::NO_LEVEL_ID ) {
                    continue;
                }
                //给推荐团队添加团队业绩 注：如果些订单特别多的情况，可以分2步。先只计算个人业绩、然后根据个人业绩再计算团队业绩。
                //给推荐关系累计增加业绩
                $this->loopRelationParentDo($data['USER_ID'], function ($parent) use ($data, $orderCashAmount) {
                    // 给上级会员追加本期业绩到缓存中
                    CalcCache::nowStandardMonthPerf($parent['PARENT_UID'], $this->_periodNum, [
                        'AMOUNT_PSS' => $orderCashAmount,
                    ]);
                    // 把该会员加入到能拿到业绩的会员缓存中
                    CalcCache::addHasStandardMonthPerfUsers($parent['PARENT_UID'], $this->_periodNum);

                    //获取parent聘级
                    $parentUserInfo = CalcCache::getUserInfo($parent['PARENT_UID'], $this->_periodNum);
                    if( $parentUserInfo['EMP_LV'] !=  EmployLevel::NO_LEVEL_ID ) {//如果碰到有聘级的业绩就不在向上累加了
                        return self::LOOP_FINISH;
                    }
                });

                unset($data, $baseInfo, $orderCashAmount);
            }

            unset($insertPerfOrderData, $allData, $snArr);
            return $this->loopCalcPerfByStandardFXOrder($offset + $this->_limit);
        }

        unset($allData);
        return true;
    }

    /**
     * 计算月业绩表相关的数据并写入数据库
     * @param int $offset
     * @return bool
     * @throws Exception
     * @throws \yii\db\Exception
     */
    public function loopCalcMonthPerfTableData(int $offset = 0) {
        if (!$this->_isCalcMonth) {
            return true;
        }
        echo sprintf("时间:[%s]月业绩，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        // 从缓存列表里面从底层往上倒序获取会员

//        $allData = PerfPeriod::findUseDbCalc()->from(PerfPeriod::tableName()  . ' AS PP')->select('PP.USER_ID AS USER_ID, SUM(PP.PV_PCS) AS PV_PCS_SUM,SUM(PP.PV_PSS) AS PV_PSS_SUM,SUM(PP.PV_1L) AS PV_1L_SUM,SUM(PP.PV_2L) AS PV_2L_SUM,SUM(PP.PV_3L) AS PV_3L_SUM,SUM(PP.PV_4L) AS PV_4L_SUM,SUM(PP.PV_5L) AS PV_5L_SUM, SUM(PM.PV_1L_TOTAL) AS PV_1L_TOTAL, SUM(PM.PV_2L_TOTAL) AS PV_2L_TOTAL, SUM(PM.PV_3L_TOTAL) AS PV_3L_TOTAL, SUM(PM.PV_4L_TOTAL) AS PV_4L_TOTAL,SUM(PM.PV_5L_TOTAL) AS PV_5L_TOTAL, SUM(PM.PV_PSS_TOTAL) AS PV_PSS_TOTAL')->leftJoin(PerfMonth::tableName()  . ' AS PM', 'PM.USER_ID=PP.USER_ID AND PM.CALC_MONTH=:LAST_CALC_MONTH', ['LAST_CALC_MONTH'=>$this->_lastCalcYearMonth])->where('PP.CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])->groupBy('PP.USER_ID')->orderBy('PP.USER_ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
        $allData = PerfPeriod::findUseDbCalc()->select('USER_ID, SUM(FX_AMOUNT_CASH) AS FX_AMOUNT_CASH_SUM,SUM(PV_PCS) AS PV_PCS_SUM,SUM(PV_PCS_FX) AS PV_PCS_FX_SUM,SUM(PV_PSS) AS PV_PSS_SUM,SUM(PV_1L) AS PV_1L_SUM,SUM(PV_2L) AS PV_2L_SUM,SUM(PV_3L) AS PV_3L_SUM,SUM(PV_4L) AS PV_4L_SUM,SUM(PV_5L) AS PV_5L_SUM,SUM(PV_1L_ZC) AS PV_1L_ZC_SUM,SUM(PV_2L_ZC) AS PV_2L_ZC_SUM,SUM(PV_3L_ZC) AS PV_3L_ZC_SUM,SUM(PV_4L_ZC) AS PV_4L_ZC_SUM,SUM(PV_5L_ZC) AS PV_5L_ZC_SUM')->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])->groupBy('USER_ID')->orderBy('USER_ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
        if ($allData) {
            // 月度业绩表
            foreach ($allData as $everyData) {
                $userId = $everyData['USER_ID'];
//                $lastMonthData = PerfMonth::find()->select('PV_1L_TOTAL, PV_2L_TOTAL, PV_3L_TOTAL, PV_4L_TOTAL, PV_5L_TOTAL, PV_PSS_TOTAL')->where('USER_ID=:USER_ID AND CALC_MONTH=:LAST_CALC_MONTH', [
//                    'USER_ID'=>$userId,
//                    'LAST_CALC_MONTH'=>$this->_lastCalcYearMonth,
//                ])->asArray()->one();
                //往期业绩
                $userLastPerf = CalcCache::userPerf($userId, $this->_periodNum);
                //本期业绩
                $periodPerf = CalcCache::nowPeriodPerf($userId, $this->_periodNum);

                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
                //级别必须为VIP
                $isVip = false;
                if( $userBaseInfo['DEC_LV'] === DeclarationLevel::VIP_LEVEL_ID ) {
                    $isVip= true;
                }
                if( $this->_sysConfig['vipBonusGoldDecLevel']['VALUE'] && $userBaseInfo['DEC_LV'] === DeclarationLevel::JIN_ZUAN_LEVEL_ID ) {
                    $isVip = true;
                }


                $nowMonthPerf = [
                    'USER_ID' => $userId,
                    'FX_AMOUNT_CASH' => $everyData['FX_AMOUNT_CASH_SUM'],
                    'PV_PCS' => $everyData['PV_PCS_SUM'],
                    'PV_PCS_FX' => $everyData['PV_PCS_FX_SUM'],
                    'PV_PSS' => $everyData['PV_PSS_SUM'],
                    'PV_1L' => $everyData['PV_1L_SUM'],
                    'PV_2L' => $everyData['PV_2L_SUM'],
                    'PV_3L' => $everyData['PV_3L_SUM'],
                    'PV_4L' => $everyData['PV_4L_SUM'],
                    'PV_5L' => $everyData['PV_5L_SUM'],

                    //VIP统计相关业绩
                    'VIP_PV_1L_ZC' => $isVip ? $everyData['PV_1L_ZC_SUM'] : 0,
                    'VIP_PV_2L_ZC' => $isVip ? $everyData['PV_2L_ZC_SUM'] : 0,
                    'VIP_PV_3L_ZC' => $isVip ? $everyData['PV_3L_ZC_SUM'] : 0,
                    'VIP_PV_4L_ZC' => $isVip ? $everyData['PV_4L_ZC_SUM'] : 0,
                    'VIP_PV_5L_ZC' => $isVip ? $everyData['PV_5L_ZC_SUM'] : 0,

                    //总数据，历史+本期。不能用上月加本月，因为上月可能没业绩，上上个月有业绩。
                    'PV_1L_TOTAL' => $periodPerf['PV_1L'] + $userLastPerf['PV_1L'],
                    'PV_2L_TOTAL' => $periodPerf['PV_2L'] + $userLastPerf['PV_2L'],
                    'PV_3L_TOTAL' => $periodPerf['PV_3L'] + $userLastPerf['PV_3L'],
                    'PV_4L_TOTAL' => $periodPerf['PV_4L'] + $userLastPerf['PV_4L'],
                    'PV_5L_TOTAL' => $periodPerf['PV_5L'] + $userLastPerf['PV_5L'],
                    'PV_PSS_TOTAL' => $periodPerf['PV_PSS'] + $userLastPerf['PV_PSS_TOTAL'],
                ];

                // 把会员的月业绩写入缓存中，以便下面的奖金计算从缓冲中获取数据效率高
                CalcCache::addHasMonthPerfUsers($userId, $this->_periodNum);
                CalcCache::nowMonthPerf($userId, $this->_periodNum, $nowMonthPerf);

                unset($userId, $everyData, $nowMonthPerf, $lastMonthData, $userBaseInfo, $isVip);
            }
            unset($allData);
            $this->loopCalcMonthPerfTableData($offset + $this->_limit);
        }

        unset($allData);
        return true;
    }

    /**
     * 计算聘级
     * @param int $offset
     * @return bool
     * @throws Exception
     * @throws \yii\db\Exception
     */
    public function loopCalcEmpLevel(int $offset = 0) {
        if (!$this->_isCalcMonth) {
            return true;
        }
        echo sprintf("时间:[%s]计算聘级，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        // 从缓存列表里面从底层往上倒序获取会员
        $allData = CalcCache::getUsers($this->_periodNum, $offset, $this->_limit);
        if ($allData) {
            // 月度业绩表
            foreach ($allData as $userId) {
                $userInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
                $nowMonthPerf = CalcCache::nowMonthPerf($userId, $this->_periodNum);
                $empLevel = $this->checkEmpLevel($userId, $nowMonthPerf, $userInfo['EMP_LV']);

                //根据用户的级别判断 能否得到级别积分
                if( $empLevel['LEVEL_SCORE'] > 0 ) {
                    CalcCache::nowMonthScore($userId, $this->_periodNum, [
                        'LEVEL_SCORE' => $empLevel['LEVEL_SCORE'],
                    ]);
                    CalcCache::addHasScoreUsers($userId, $this->_periodNum);
                }


                //更新月业绩的聘级和用户信息中的聘级
                CalcCache::nowMonthPerf($userId, $this->_periodNum, [
                    'EMP_LEVEL' => $empLevel['ID']
                ]);

                //为业绩单更新结算时聘级
//                    PerfOrder::updateAll(['LAST_EMP_LV'=>$empLevel['ID']],'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM',[':USER_ID'=>$userId,':PERIOD_NUM'=>$this->_periodNum]);
                $userEmpLevel = $this->_empLevelConfig[$userInfo['EMP_LV']];
                $userEmpLevelSort = $userEmpLevel['SORT'] ?? EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'];
                //不降级
                if( $empLevel['SORT'] <= $userEmpLevelSort ) continue;

                $userInfo['EMP_LV'] = $empLevel['ID'];
                CalcCache::setUserInfo($userId, $this->_periodNum, $userInfo);

                //可以判断用户是否升级，能否得到升级积分
                //因为默认是不降级，直接获得升级积分
                $upgradeScore = 0;
                foreach ($this->_empLevelConfig as $everyEmpLevel) {
                    if( $everyEmpLevel['SORT'] <= $userEmpLevelSort ) continue;
                    if( $everyEmpLevel['SORT'] > $empLevel['SORT'] ) continue;

                    $upgradeScore += $everyEmpLevel['UPGRADE_SCORE'];

                    unset($everyEmpLevel);
                }

                if( $upgradeScore > 0 ) {
                    CalcCache::nowMonthScore($userId, $this->_periodNum, [
                        'UPGRADE_SCORE' => $upgradeScore
                    ]);
                    CalcCache::addHasScoreUsers($userId, $this->_periodNum);
                }

                unset($userId, $empLevel, $nowMonthPerf, $userInfo, $userEmpLevel, $userEmpLevelSort, $upgradeScore);
            }
            unset($allData);
            $this->loopCalcEmpLevel($offset + $this->_limit);
        }

        unset($allData);
        return true;
    }

    /**
     * 计算公司总业绩
     * @return bool
     * @throws Exception
     */
    public function calcPerfCompany() {
        if (!$this->_isCalcMonth) {
            return true;
        }
        $perfCompany = PerfCompany::findOne(['CALC_MONTH' => $this->_calcYearMonth]);
        $db = \Yii::$app->db;
        $transaction = $db->beginTransaction();
        try {
            if (!$perfCompany) {
                $perfCompany = new PerfCompany();
            }
            $perfCompany->PV = $this->_companyMonthPerf;
            $perfCompany->CREATED_AT = Date::nowTime();
            $perfCompany->CALC_YEAR = $this->_calcYear;
            $perfCompany->CALC_MONTH = $this->_calcYearMonth;
            if (!$perfCompany->save()) {
                throw new Exception(Form::formatErrorsForApi($perfCompany->getErrors()));
            }
            $transaction->commit();
        } catch (Exception $e) {
            $transaction->rollBack();
            throw new Exception($e->getMessage());
        }
        unset($perfCompany);
        return true;
    }

    /**
     * 循环有业绩会员，并入库
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function loopWriteNowPerf($offset = 0) {
        echo sprintf("时间:[%s]缓存本期业绩数据入库，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        // 从缓存列表里面从底层往上倒序获取会员
        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
        if($allData){
            $insertDataPeriodPerf = [];
            foreach($allData as $userId){
                $insertDataPeriodPerf[] = $this->nowPeriodPerfData($userId);
                unset($userId);
            }
            PerfPeriod::batchInsert($insertDataPeriodPerf);

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

    /**
     * 循环有月业绩会员，并入库
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function loopWriteMonthPerf($offset = 0) {
        if(!$this->_isCalcMonth){
            return true;
        }
        echo sprintf("时间:[%s]缓存本月业绩数据入库，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        // 从缓存列表里面从底层往上倒序获取会员
        $allData = CalcCache::getHasMonthPerfUsers($this->_periodNum, $offset, $this->_limit);
        if($allData){
            $insertDataMonthPerf = [];
            foreach($allData as $userId){
                $insertDataMonthPerf[] = $this->nowMonthPerfData($userId);
                unset($userId);
            }

            PerfMonth::batchInsert($insertDataMonthPerf);
            unset($insertDataMonthPerf, $allData);
            return $this->loopWriteMonthPerf($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }


    /**
     * 循环达标业绩会员，并入库
     * @param int $offset
     * @return bool
     * @throws \yii\db\Exception
     */
    public function loopWriteStandardPerf($offset = 0) {
        if(!$this->_isCalcMonth){
            return true;
        }
        echo sprintf("时间:[%s]缓存达标业绩数据入库，当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
        // 从缓存列表里面从底层往上倒序获取会员
        $allData = CalcCache::getHasStandardMonthPerfUsers($this->_periodNum, $offset, $this->_limit);
        if($allData){
            $insertDataStandardPerf = [];
            foreach($allData as $userId) {
                $data = CalcCache::nowStandardMonthPerf($userId, $this->_periodNum);
                $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
                $insertDataStandardPerf[] = [
                    'ID' => SnowFake::instance()->generateId(),
                    'USER_ID' => $userId,
                    'LAST_DEC_LV' => $baseInfo['DEC_LV'],
                    'LAST_EMP_LV' => $baseInfo['EMP_LV'],
                    'LAST_STATUS' => $baseInfo['STATUS'],
                    'AMOUNT_PCS' => $data['AMOUNT_PCS'],
                    'AMOUNT_PSS' => $data['AMOUNT_PSS'],
                    'CALC_MONTH' => $this->_calcYearMonth,
                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
                    'CREATED_AT' => Date::nowTime(),
                ];
                unset($data, $baseInfo, $userId);
            }

            PerfStandard::batchInsert($insertDataStandardPerf);
            unset($insertDataStandardPerf, $allData);
            return $this->loopWriteStandardPerf($offset + $this->_limit);
        }
        unset($allData);
        return true;
    }

    /**
     * 本期业绩数据
     * @param $userId
     * @return array
     */
    public function nowPeriodPerfData($userId){
        $data = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
        $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
        $result = [
            'ID' => SnowFake::instance()->generateId(),
            'USER_ID' => $userId,
            'LAST_DEC_LV' => $baseInfo['DEC_LV'],
            'LAST_EMP_LV' => $baseInfo['EMP_LV'],
            'LAST_STATUS' => $baseInfo['STATUS'],
            'FX_AMOUNT_CASH' => $data['FX_AMOUNT_CASH'],
            'PV_PCS' => $data['PV_PCS'],
            'PV_PSS' => $data['PV_PSS'],
            'PV_PCS_ZC' => $data['PV_PCS_ZC'],
            'PV_PCS_YH' => $data['PV_PCS_YH'],
            'PV_PCS_ZG' => $data['PV_PCS_ZG'],
            'PV_PCS_LS' => $data['PV_PCS_LS'],
            'PV_PCS_FX' => $data['PV_PCS_FX'],
            'PV_PCS_FX_CASH' => $data['PV_PCS_FX_CASH'],
            'PV_PCS_FX_POINT' => $data['PV_PCS_FX_POINT'],
            'PV_1L' => $data['PV_1L'],
            'PV_1L_TOUCH' => $data['PV_1L_TOUCH'],
            'PV_1L_ZC' => $data['PV_1L_ZC'],
            'PV_1L_YH' => $data['PV_1L_YH'],
            'PV_1L_ZG' => $data['PV_1L_ZG'],
            'PV_1L_LS' => $data['PV_1L_LS'],
            'PV_1L_FX' => $data['PV_1L_FX'],
            'PV_2L' => $data['PV_2L'],
            'PV_2L_TOUCH' => $data['PV_2L_TOUCH'],
            'PV_2L_ZC' => $data['PV_2L_ZC'],
            'PV_2L_YH' => $data['PV_2L_YH'],
            'PV_2L_ZG' => $data['PV_2L_ZG'],
            'PV_2L_LS' => $data['PV_2L_LS'],
            'PV_2L_FX' => $data['PV_2L_FX'],
            'PV_3L' => $data['PV_3L'],
            'PV_3L_TOUCH' => $data['PV_3L_TOUCH'],
            'PV_3L_ZC' => $data['PV_3L_ZC'],
            'PV_3L_YH' => $data['PV_3L_YH'],
            'PV_3L_ZG' => $data['PV_3L_ZG'],
            'PV_3L_LS' => $data['PV_3L_LS'],
            'PV_3L_FX' => $data['PV_3L_FX'],
            'PV_4L' => $data['PV_4L'],
            'PV_4L_TOUCH' => $data['PV_4L_TOUCH'],
            'PV_4L_ZC' => $data['PV_4L_ZC'],
            'PV_4L_YH' => $data['PV_4L_YH'],
            'PV_4L_ZG' => $data['PV_4L_ZG'],
            'PV_4L_LS' => $data['PV_4L_LS'],
            'PV_4L_FX' => $data['PV_4L_FX'],
            'PV_5L' => $data['PV_5L'],
            'PV_5L_TOUCH' => $data['PV_5L_TOUCH'],
            'PV_5L_ZC' => $data['PV_5L_ZC'],
            'PV_5L_YH' => $data['PV_5L_YH'],
            'PV_5L_ZG' => $data['PV_5L_ZG'],
            'PV_5L_LS' => $data['PV_5L_LS'],
            'PV_5L_FX' => $data['PV_5L_FX'],
            'PV_LS_TOUCH' => $data['PV_LS_TOUCH'],
            'SURPLUS_1L' => $data['SURPLUS_1L'],
            'SURPLUS_2L' => $data['SURPLUS_2L'],
            'SURPLUS_3L' => $data['SURPLUS_3L'],
            'SURPLUS_4L' => $data['SURPLUS_4L'],
            'SURPLUS_5L' => $data['SURPLUS_5L'],
            'SURPLUS_LS' => $data['SURPLUS_LS'],
            'PERIOD_NUM' => $this->_periodNum,
            'CALC_MONTH' => $this->_calcYearMonth,
            'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
            'CREATED_AT' => Date::nowTime(),
        ];
        unset($data);
        return $result;
    }



    /**
     * 本月业绩
     * @param $userId
     * @return array
     */
    public function nowMonthPerfData($userId){
        $data = CalcCache::nowMonthPerf($userId, $this->_periodNum);
        $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
        $result = [
            'ID' => SnowFake::instance()->generateId(),
            'USER_ID' => $userId,
            'LAST_DEC_LV' => $baseInfo['DEC_LV'],
            'LAST_EMP_LV' => $data['EMP_LEVEL'],
            'LAST_STATUS' => $baseInfo['STATUS'],
            'FX_AMOUNT_CASH' => $data['FX_AMOUNT_CASH'],
            'PV_PCS' => $data['PV_PCS'],
            'PV_PCS_FX' => $data['PV_PCS_FX'],
            'PV_PSS' => $data['PV_PSS'],
            'PV_1L' => $data['PV_1L'],
            'PV_2L' => $data['PV_2L'],
            'PV_3L' => $data['PV_3L'],
            'PV_4L' => $data['PV_4L'],
            'PV_5L' => $data['PV_5L'],
            'VIP_PV_1L_ZC' => $data['VIP_PV_1L_ZC'],
            'VIP_PV_2L_ZC' => $data['VIP_PV_2L_ZC'],
            'VIP_PV_3L_ZC' => $data['VIP_PV_3L_ZC'],
            'VIP_PV_4L_ZC' => $data['VIP_PV_4L_ZC'],
            'VIP_PV_5L_ZC' => $data['VIP_PV_5L_ZC'],
            'PV_1L_TOTAL' => $data['PV_1L_TOTAL'],
            'PV_2L_TOTAL' => $data['PV_2L_TOTAL'],
            'PV_3L_TOTAL' => $data['PV_3L_TOTAL'],
            'PV_4L_TOTAL' => $data['PV_4L_TOTAL'],
            'PV_5L_TOTAL' => $data['PV_5L_TOTAL'],
            'PV_PSS_TOTAL' => $data['PV_PSS_TOTAL'],
            'CF_PERCENT' => $data['CF_PERCENT'],
            'LX_PERCENT' => $data['LX_PERCENT'],
            'FX_STATUS' => $data['FX_STATUS'],
            'CALC_MONTH' => $this->_calcYearMonth,
            'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
            'CREATED_AT' => Date::nowTime(),
        ];
        unset($data);
        return $result;
    }

    /**
     * 循环父级并执行回调函数
     * @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;
    }

    /**
     * ==== 聘级计算开始 ====
     */

    /**
     * 查看会员聘级
     * @param $userId
     * @param $nowMonthPerf
     * @param $userEmpLevelId
     * @return bool|mixed|\yii\db\ActiveRecord
     */
    public function checkEmpLevel($userId, $nowMonthPerf, $userEmpLevelId) {
        $empLevel = $this->_empLevelConfig;
        $userEmpLevel = $empLevel[$userEmpLevelId];
        $userEmpLevelSort = $userEmpLevel['SORT'] ?? EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'];
        $childEmpLevelNumArr = CalcCache::hasEmpLevelNum($userId, $this->_periodNum);

        // 判断主任到首席总监
        $resultLevel = $this->isEmpLevelOther($empLevel, $childEmpLevelNumArr, $userEmpLevelSort);
        unset($empLevel, $childEmpLevelNumArr);

        if( empty($resultLevel) ) {
            //不降级
            if ( $userEmpLevelSort < EmployLevel::EMP_LEVEL_SORT['JX_ZR_LEVEL'] ) {
                //主任
                if ($checkLevel = $this->isEmpLevel1($userId, $nowMonthPerf)) {
                    $resultLevel = $checkLevel;
                } else {// 无聘级会员
                    $resultLevel = EmployLevel::getLevelFromSort(EmployLevel::EMP_LEVEL_SORT['NO_LEVEL']);
                }
            }else {
                $resultLevel = $userEmpLevel;
            }
        }
        unset($userEmpLevel, $userEmpLevelSort);

        // 获取到级别以后，给上级的相应人数中追加数量
        $parentRecUserId = $userId;
        $this->loopRelationParentDo($userId, function ($parent) use ($resultLevel, &$parentRecUserId) {
            if( !$parentRecUserId ) return self::LOOP_FINISH;
            //判断$parentRecUserId是否为$parent['PARENT_UID']的直推
            $parentUid = $parent['PARENT_UID'];
            $toInfo = CalcCache::getUserInfo($parentRecUserId, $this->_periodNum);
            if( !isset($toInfo['REC_UID']) ) {
                echo $parentRecUserId . PHP_EOL;
            }
            if( isset($toInfo['REC_UID']) && $parentUid !== $toInfo['REC_UID'] ) {
                $parentUid = $toInfo['REC_UID'];
            }
            unset($toInfo);
            if( !$parentUid ) self::LOOP_FINISH;

            CalcCache::hasEmpLevelNum($parentUid, $this->_periodNum, [$parentRecUserId => [$resultLevel['ID'] => 1]]);
            //每次记录上次的USER_ID
            $parentRecUserId = $parentUid;
        });
        return $resultLevel;
    }

    /**
     * 是否达到主任
     * @param $userId
     * @param $nowMonthPerf
     * @return bool|mixed|\yii\db\ActiveRecord
     * @throws \yii\db\Exception
     */
    public function isEmpLevel1($userId, $nowMonthPerf) {
        $level1Option = EmployLevel::getLevelFromSort(EmployLevel::EMP_LEVEL_SORT['JX_ZR_LEVEL']);
        //条件去除最大部门，其它部门累计50万
        if( $nowMonthPerf['PV_PSS_TOTAL'] < $level1Option['OTHER_DEPART_PERF'] ) {//本身业绩就小于50万
            unset($level1Option);
            return false;
        }

        $oneDeepRelation = CalcCache::getChildrenOneDeepFromRedis($userId, $this->_periodNum);
        if (  count($oneDeepRelation) <= 1 ) {//只有一个区就不判断了
            unset($oneDeepRelation, $level1Option);
            return false;
        }
        $maxPvPSS = 0;
        $childPvPssTotalSum = 0;
        foreach ($oneDeepRelation as $childData) {
            //往期业绩
            $userLastPerf = CalcCache::userPerf($childData['USER_ID'], $this->_periodNum);
            //本期业绩
            $periodPerf = CalcCache::nowPeriodPerf($childData['USER_ID'], $this->_periodNum);
            $pvPcsTotal = $userLastPerf['PV_PCS_ZC'] + $userLastPerf['PV_PCS_FX'] + $periodPerf['PV_PCS'];

            $pvPssTotal = $userLastPerf['PV_PSS_TOTAL'] + $periodPerf['PV_PSS'];
            $childPvPssTotal = $pvPcsTotal + $pvPssTotal;
            unset($userLastPerf, $periodPerf, $pvPcsTotal, $pvPssTotal);

            $childPvPssTotalSum += $childPvPssTotal;
            if( $childPvPssTotal >= $maxPvPSS ) {
                $maxPvPSS = $childPvPssTotal;
            }

            unset($childData, $childPvPssTotal);
        }
        unset($oneDeepRelation);

        if( $childPvPssTotalSum - $maxPvPSS >= $level1Option['OTHER_DEPART_PERF'] ) {
            unset($maxPvPSS, $childPvPssTotalSum);
            return $level1Option;
        }else {
            unset($maxPvPSS, $childPvPssTotalSum, $level1Option);
            return false;
        }
    }

    /**
     * 是否达到其他级别
     * @param $empLevel
     * @param $childEmpLevelNumArr
     * @param $userEmpLevelSort
     * @return bool|mixed|\yii\db\ActiveRecord
     */
    public function isEmpLevelOther($empLevel, $childEmpLevelNumArr, $userEmpLevelSort) {
        $resultLevel = [];
        foreach ($empLevel as $level) {
            if ($level['SORT'] < EmployLevel::EMP_LEVEL_SORT['JX_ZR_LEVEL']) continue;
            //不降级
            if ($level['SORT'] <= $userEmpLevelSort) continue;

            // 级别要求有几个区
            $locationNum = $level['LOCATION_NUM'];
            // 级别要求这几个区分别存在级别会员的数量
            $minEmpNum = $level['MIN_EMPLOY_NUM'];
            $minEmpLevel = $level['MIN_EMPLOY_LEVEL'];
            if( !$minEmpLevel || !isset($empLevel[$minEmpLevel]) || $minEmpNum < 1 ) continue;
            $minEmpLevelSort = $empLevel[$minEmpLevel]['SORT'];
            $resultEmpLevelNumArr = array_filter($childEmpLevelNumArr, function ($item, $departUserId) use ($empLevel, $minEmpLevelSort, $minEmpNum) {

                $tempEmpLevelNum = 0;
                foreach ($item as $levelIndex => $levelNum) {
                    if ($empLevel[$levelIndex]['SORT'] >= $minEmpLevelSort && $levelNum >= $minEmpNum) {
                        $tempEmpLevelNum += 1;
                    }
                }
                return $tempEmpLevelNum >= 1;
            }, ARRAY_FILTER_USE_BOTH);
            if (count($resultEmpLevelNumArr) >= $locationNum) {
                if( $level['SORT'] == EmployLevel::EMP_LEVEL_SORT['SHX_ZJ_LEVEL'] ) {
                    //首席总监还要满足一个条件、任意部门1个高级总监
                    $gjZjLevelNumArr = array_filter($childEmpLevelNumArr, function ($item, $departUserId) use ($empLevel) {

                        $tempEmpLevelNum = 0;
                        foreach ($item as $levelIndex => $levelNum) {
                            if ($empLevel[$levelIndex]['SORT'] >= EmployLevel::EMP_LEVEL_SORT['GJ_ZJ_LEVEL'] && $levelNum >= 1) {
                                $tempEmpLevelNum += 1;
                            }
                        }
                        return $tempEmpLevelNum >= 1;
                    }, ARRAY_FILTER_USE_BOTH);
                    if( count($gjZjLevelNumArr) >= 1 ) {
                        $resultLevel = $level;
                    }
                    unset($gjZjLevelNumArr);
                }else {
                    $resultLevel = $level;
                }
            }
            unset($level, $minEmpLevel, $minEmpLevelSort, $resultEmpLevelNumArr);
        }
        unset($empLevel, $childEmpLevelNumArr, $userEmpLevelSort);
        return $resultLevel;
    }

    /**
     * 是否可拿业绩（即注销、关停、停发状态）
     * @param $userId
     * @return bool
     */
    public function isHasPerf($userId) {
        //@todo 所有人都有业绩
        return true;
    }

    /**
     * ==== 聘级计算结束 ====
     */

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

}