SettleTeamAward.php 13 KB

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