SettleTeamAward.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. declare (strict_types = 1);
  3. namespace app\api\command;
  4. use app\api\controller\h5\User;
  5. use app\common\model\BoxHandleModel;
  6. use app\common\model\MoneyLogModel;
  7. use app\common\model\SubmeterModel;
  8. use app\common\model\UserModel;
  9. use app\common\service\LevelSettingService;
  10. use think\console\Command;
  11. use think\console\Input;
  12. use think\console\Output;
  13. use think\facade\Db;
  14. use utils\RedisCache;
  15. /**
  16. * 结算团队收益 by wes 每天晚上21点后运行1次
  17. * Class SettleTeamAward
  18. * @package app\api\command
  19. */
  20. class SettleTeamAward extends Command
  21. {
  22. protected $cacheTime = 86400; // 一天
  23. protected function configure()
  24. {
  25. $this->setName('settle_team_award')
  26. ->setDescription('the settle_team_award command');
  27. }
  28. /**
  29. * 处理分表
  30. * @param Input $input
  31. * @param Output $output
  32. * @return int
  33. */
  34. protected function execute(Input $input, Output $output)
  35. {
  36. $cacheKey = "caches:settleTeamAward:".date('Ymd').":";
  37. if(RedisCache::get($cacheKey.'lock')){
  38. echo json_encode(['code'=>'error','msg'=>'请不要频繁提交,正在结算中稍后再试~','date'=>date('Y-m-d H:i:s')],256)."\n";
  39. return false;
  40. }
  41. if(date('H:i') < '21:00'){
  42. echo json_encode(['code'=>'error','msg'=>'未到结算时间,结算时间为每日21:00后~','date'=>date('Y-m-d H:i:s')],256)."\n";
  43. return false;
  44. }
  45. RedisCache::setnx($cacheKey.'lock', date('Y-m-d H:i:s'), rand(30, 60));
  46. Db::startTrans();
  47. try {
  48. if($result = $this->settleTeamAward()){
  49. echo json_encode($result, 256)."\n";
  50. Db::commit();
  51. }
  52. } catch (\Exception $e) {
  53. Db::rollback();
  54. $log = ['msg'=> $e->getMessage(),'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')];
  55. RedisCache::set($cacheKey."fail", $log, 5 * 3600);
  56. RedisCache::clear($cacheKey.'lock');
  57. echo json_encode(['code'=>'error','msg'=>$e->getMessage(),'date'=>date('Y-m-d H:i:s')], 256)."\n";
  58. }
  59. return true;
  60. }
  61. /**
  62. * 团队奖金结算
  63. * @return array
  64. * @throws \think\Exception
  65. * @throws \think\db\exception\DataNotFoundException
  66. * @throws \think\db\exception\DbException
  67. * @throws \think\db\exception\ModelNotFoundException
  68. */
  69. public function settleTeamAward()
  70. {
  71. // 获取开出的福袋盒子
  72. $boxList = BoxHandleModel::alias('a')
  73. ->leftJoin('user u','u.id=a.uid')
  74. ->where('a.is_team_handle', '0')
  75. ->where('a.open_time', '<=', date('Y-m-d H:i:s'))
  76. ->field('a.id,a.uid,a.rid,u.pid,u.level,u.path,u.has_fd,u.money')
  77. ->order('a.id desc')
  78. ->select();
  79. $boxList = $boxList? $boxList->toArray() : [];
  80. if(empty($boxList)){
  81. sr_throw('暂无团队奖金可结算');
  82. }
  83. // 获取等级配置
  84. $levelConfig = LevelSettingService::make()->getConfigData(0,1);
  85. if(empty($levelConfig)){
  86. sr_throw('请先配置等级参数');
  87. }
  88. $date = date('Y-m-d H:i:s');
  89. $catchIds = $moneyLogs = []; // 处理的数据
  90. $awardCounts = [];
  91. $cacheKey = "caches:settleTeamAward:".date('Ymd').":";
  92. foreach ($boxList as $k => $val){
  93. $boxId = isset($val['id'])? $val['id'] : 0;
  94. $uid = isset($val['uid'])? $val['uid'] : 0;
  95. $pid = isset($val['pid'])? $val['pid'] : 0;
  96. $path = isset($val['path'])? $val['path'] : '';
  97. if($pid<=0 || empty($path)){
  98. $catchIds[] = $boxId;
  99. continue;
  100. }
  101. /* TODO 验证上级是否存在,并且计算直推奖金*/
  102. $pInfo = UserModel::where(['id'=> $pid])
  103. ->field('id,money,level,pid,path,has_fd,total_appoint_count,store_expire_time')
  104. ->find();
  105. $pHasFd = isset($pInfo['has_fd'])? intval($pInfo['has_fd']) : 0;
  106. $pLevel = isset($pInfo['level'])? intval($pInfo['level']) : 0;
  107. $appointCount = isset($pInfo['total_appoint_count'])? intval($pInfo['total_appoint_count']) : 0;
  108. $storeExpireTime = isset($pInfo['store_expire_time']) && $pInfo['store_expire_time']? strtotime($pInfo['store_expire_time']) : 0;
  109. if($pInfo && $pHasFd){
  110. // 计算上级V0以上,当前等级的直推奖金:v1-1元,v2-2元,v3-3元,v4-1元(没分完得)
  111. $ztAward = isset($levelConfig[$pLevel]['zt_money'])? floatval($levelConfig[$pLevel]['zt_money']) : 0;
  112. if($pLevel>0 && $ztAward>0){
  113. $pMoney = isset($pInfo['money'])? $pInfo['money'] : 0;
  114. // 今日直推奖金
  115. $todayZtMoney = ($appointCount>=0 && $storeExpireTime>=time())? $ztAward : 0;
  116. // 直推奖流水
  117. $log = [
  118. 'uid'=> $pid,
  119. 'type'=> 5,
  120. 'money'=> $ztAward,
  121. 'create_at'=> sr_getcurtime(time()),
  122. 'state'=> 1,
  123. 'before_money'=> $pMoney,
  124. 'after_money'=> floatval($pMoney + $ztAward),
  125. 'from_id'=> $boxId,
  126. 'uid2'=> $uid,
  127. 'free_type'=> 0,
  128. 'remark'=> "V{$pLevel}直推1个福袋奖金".($todayZtMoney>0? ",有今日奖金{$todayZtMoney}":'')
  129. ];
  130. $moneyLogs[] = $log;
  131. if(!UserModel::where('id', $pid)->inc('money', $ztAward)->inc('today_money', $todayZtMoney)->update()){
  132. $logData = ['ztAward'=>$ztAward,'data'=> $val,'pInfo'=> $pInfo,'log'=>$log,'msg'=>"直推用户[{$pid}]结算直推奖错误",'date'=> $date];
  133. RedisCache::set($cacheKey."box_{$uid}_{$boxId}:{$pid}_{$pLevel}_error", $logData, 3*3600);
  134. sr_throw("直推用户[{$pid}]结算直推奖错误");
  135. }
  136. // 累计当前用户获得的奖金
  137. $awardCounts[$pid] = isset($awardCounts[$pid])? round($awardCounts[$pid]+$ztAward, 2) : $ztAward;
  138. }
  139. }
  140. /* TODO 计算团队奖金 */
  141. $maxLevel = 0;
  142. $awardTotal = 0;
  143. $teamMoneys = [];
  144. $teamAwardEnd = $sameLevelAwardEnd = 0; // 是否给完奖金
  145. $pIds = explode(',', $path);
  146. $pIds = array_reverse($pIds);
  147. $ids = implode(',', $pIds);
  148. $order = 'field(id,' . $ids . ')';
  149. $teamList = UserModel::whereIn('id', $pIds)
  150. ->field('id,money,level,has_fd,total_appoint_count,store_expire_time')
  151. ->order(Db::raw($order))
  152. ->select();
  153. $teamList = $teamList? $teamList->toArray() : [];
  154. if(!empty($teamList)){
  155. foreach($teamList as $key => $item){
  156. // 计算团队奖金
  157. $tId = isset($item['id'])? $item['id'] : 0;
  158. $tLevel = isset($item['level'])? $item['level'] : 0;
  159. $tLastId = isset($teamList[$key-1]['id'])? intval($teamList[$key-1]['id']) : 0;
  160. $tLastLevel = isset($teamList[$key-1]['level'])? intval($teamList[$key-1]['level']) : 0; // 前一个人的等级
  161. $tMoney = isset($item['money'])? $item['money'] : 0;
  162. $tHasFd = isset($item['has_fd'])? $item['has_fd'] : 0;
  163. $tAppointCount = isset($item['total_appoint_count'])? intval($item['total_appoint_count']) : 0;
  164. $tStoreExpireTime = isset($item['store_expire_time']) && !empty($item['store_expire_time'])? strtotime($item['store_expire_time']) : 0;
  165. $levelData = isset($levelConfig[$tLevel])? $levelConfig[$tLevel] : [];
  166. if($key == 0){
  167. // 等级大于V0
  168. if($tLevel > 0){
  169. $teamAward = isset($levelData['dynamic_scale'])? round($levelData['dynamic_scale'], 2) : 0;
  170. if($teamAward>0){
  171. $teamMoneys[$tId] = $teamAward;
  172. $awardTotal += $teamAward;
  173. $maxLevel = $tLevel;
  174. // V4以及以上等级直接分完后结束,不再往下分
  175. if($tLevel >= 4){
  176. $teamAwardEnd = true;
  177. }
  178. }
  179. }
  180. }
  181. // 如果未分完,且等级比上一级和当前分到到用户等级都高,则再分剩余奖金
  182. else if(!$teamAwardEnd && $tLevel > $tLastLevel && $tLevel > $maxLevel){
  183. $teamAward = isset($levelData['dynamic_scale'])? floatval($levelData['dynamic_scale']) : 0;
  184. $teamAward = max(0, $teamAward - $awardTotal);
  185. if($teamAward>0){
  186. $teamMoneys[$tId] = $teamAward;
  187. $awardTotal += $teamAward;
  188. }
  189. }
  190. /* TODO 计算平级奖金:未分完(金额小于0.1)情况下一直分 */
  191. $sameTeamAward = 0;
  192. if($key>0 && !$sameLevelAwardEnd){
  193. if($tLevel == $tLastLevel){
  194. $sameAward = isset($teamMoneys[$tLastId])? floatval($teamMoneys[$tLastId]) : 0;
  195. $sameTeamAward = round($sameAward * 0.1, 2);
  196. $sameTeamAward = $sameTeamAward>=0.1? $sameTeamAward : 0;
  197. if($sameTeamAward>0){
  198. $teamMoneys[$tId] = $sameTeamAward;
  199. }
  200. }
  201. }
  202. /* TODO 当前用户最终是否有团队奖金,有且预约过福袋则入账 */
  203. $endTeamAward = isset($teamMoneys[$tId])? floatval($teamMoneys[$tId]) : 0;
  204. if($endTeamAward>0 && $tAppointCount>0){
  205. // 今日团队奖金
  206. $tTodayAward = ($appointCount>=10 && $tStoreExpireTime >= time())? $endTeamAward : 0;
  207. // 流水
  208. $log = [
  209. 'uid'=> $tId,
  210. 'type'=> 7,
  211. 'money'=> $endTeamAward,
  212. 'create_at'=> sr_getcurtime(time()),
  213. 'state'=> 1,
  214. 'before_money'=> $tMoney,
  215. 'after_money'=> floatval($tMoney + $endTeamAward),
  216. 'from_id'=> $boxId,
  217. 'uid2'=> $sameTeamAward? $tLastId : $uid,
  218. 'free_type'=> $sameTeamAward? 0 : 1,
  219. 'remark'=> ($sameTeamAward? "V{$tLevel}得[ID:{$tLastId}]1个福袋平级奖":"V{$tLevel}得[ID:{$uid}]1个福袋团队奖").($tTodayAward>0? ",有今日奖金{$tTodayAward}":'')
  220. ];
  221. $moneyLogs[] = $log;
  222. if(!UserModel::where('id', $tId)->inc('money', $endTeamAward)->inc('today_money', $tTodayAward)->update()){
  223. $logData = ['endAward'=>$endTeamAward,'todayAward'=>$tTodayAward,'data'=> $val,'tInfo'=> $item,'log'=>$log,'msg'=>"用户[{$tId}]结算团队奖错误",'date'=> $date];
  224. RedisCache::set($cacheKey."box_{$uid}_{$boxId}:{$pid}_{$pLevel}_error", $logData, 3*3600);
  225. sr_throw("用户[{$tId}]结算团队奖错误");
  226. }
  227. $awardCounts[$tId] = isset($awardCounts[$tId])? round($awardCounts[$tId]+$endTeamAward, 2) : $endTeamAward;
  228. }
  229. }
  230. }
  231. $catchIds[] = $boxId;
  232. $logData = ['teamAwards'=>$teamMoneys,'data'=> $val,'teamAward'=> $awardTotal,'msg'=>"盒子[{$boxId}]团队奖金处理完成",'date'=> $date];
  233. RedisCache::set($cacheKey."box_{$uid}_{$boxId}:catch", $logData, 3*3600);
  234. }
  235. if(empty($catchIds)){
  236. sr_throw('结算失败,没有结算成功数据');
  237. }
  238. // 更新处理记录状态
  239. if(!BoxHandleModel::whereIn('id', $catchIds)->save(['is_team_handle' => 1,'update_time'=> $date])){
  240. sr_throw('更新处理数据状态失败');
  241. }
  242. // 写入奖金流水
  243. if($moneyLogs && !MoneyLogModel::insertAll($moneyLogs)){
  244. sr_throw('处理奖金流水错误');
  245. }
  246. $logData = ['count'=> count($catchIds),'data'=> $val,'awards'=> $awardCounts,'msg'=>"团队奖金结算成功",'date'=> $date];
  247. RedisCache::set($cacheKey."result", $logData, 3*3600);
  248. return ['code'=>200, 'msg'=> "团队奖金结算成功,累计结算记录".count($catchIds).",结算人数".count($awardCounts)."人,总奖金".array_sum($awardCounts),'date'=> $date];
  249. }
  250. }