ScoreBonusSettleService.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <?php
  2. namespace addons\ScoreBonus\common\services;
  3. use yii\data\Pagination;
  4. use Yii;
  5. use common\helpers\DateHelper;
  6. use common\helpers\FormatHelper;
  7. use common\enums\StatusEnum;
  8. use common\enums\UserWalletEnum;
  9. use common\models\user\UserWallet;
  10. use services\common\UserWalletService;
  11. use addons\ScoreBonus\common\models\ScoreBonusModel;
  12. use addons\ScoreBonus\common\models\ScoreBonusRewardLogModel;
  13. use addons\ScoreBonus\common\models\ScoreBonusCapitalModel;
  14. use addons\ScoreBonus\common\models\ScoreBonusErrorLogModel;
  15. use addons\ScoreBonus\common\enums\ScoreBonusEnums;
  16. use addons\ScoreBonus\common\models\ScoreBonusCapitalPool;
  17. use addons\ScoreBonus\common\models\ScoreBonusUserWeight;
  18. use Exception;
  19. /**
  20. *
  21. * 应用置顶服务
  22. */
  23. class ScoreBonusSettleService
  24. {
  25. // 积分分红结算
  26. public function scoreBonusSettle($mall_id = '', $config = [])
  27. {
  28. try {
  29. if (empty($mall_id) || empty($config)) throw new \Exception('无商城id或商城配置');
  30. $where = [
  31. 'mall_id' => $mall_id,
  32. 'status' => [StatusEnum::ENABLED,StatusEnum::DISABLED],
  33. ];
  34. $today = DateHelper::today();
  35. switch ($config['settle_circle']) {
  36. case 1: //结算周期为周
  37. $this_week = DateHelper::thisWeek();
  38. if (date("Ymd", $this_week['start']) != date("Ymd", $today['start'])) throw new \Exception('非每周第一天,终止结算');
  39. //正式代码
  40. $last_week = DateHelper::lastWeek();
  41. $date_time = date('Ymd', $last_week['end']);
  42. break;
  43. case 2://结算周期为月
  44. $this_month = DateHelper::thisMonth();
  45. if (date("Ymd", $this_month['start']) != date("Ymd", $today['start'])) throw new \Exception('非每月第一天,终止结算');
  46. $last_month = DateHelper::lastMonth();
  47. $date_time = date('Ymd', $last_month['end']);
  48. break;
  49. case 3://结算周期为天
  50. $last_day = DateHelper::yesterDay();
  51. $date_time = date('Ymd', $last_day['start']);
  52. break;
  53. default:
  54. throw new \Exception('非有效结算周期');
  55. break;
  56. }
  57. //测试代码
  58. // $date_time = date('Ymd', time());
  59. // $where['date_time'] = $date_time;
  60. //获取最新结算的一期
  61. $check_score_bonus_data = ScoreBonusModel::getDataOne($where, 'id desc');
  62. //检查本次结算是否已结算
  63. if (!empty($check_score_bonus_data) && $check_score_bonus_data['date_time'] == $date_time) throw new \Exception("该周期已结算");
  64. // print_r($check_score_bonus_data);exit;
  65. //开始结算分红
  66. //获取未结算资金池
  67. //获取分红池数据
  68. $capital = ScoreBonusCapitalPool::getOne([
  69. ['mall_id' => $mall_id]
  70. ]);
  71. // $where['status'] = StatusEnum::DISABLED;
  72. // $capital_pool = ScoreBonusCapitalModel::getFieldSum($where, 'bonus_price');
  73. $capital_pool = $capital->bonus_price;
  74. if ($config['settle_method']) {
  75. //新模式 权重占比分配
  76. //平台符合条件用户的总权重
  77. $all_user_weight = ScoreBonusUserWeight::find()
  78. ->alias('w')
  79. ->joinWith(['userWallet uw' => function($query) {
  80. $query->andWhere(['>', 'uw.score', 0]);
  81. }])
  82. ->where(['w.status' => 1, 'w.mall_id' => $mall_id])
  83. ->andWhere(['>', 'weight', 0])
  84. ->sum('weight');
  85. //平台符合条件用户的总积分
  86. $all_user_score = ScoreBonusUserWeight::find()
  87. ->alias('w')
  88. ->joinWith(['userWallet uw' => function($query) {
  89. $query->andWhere(['>', 'uw.score', 0]);
  90. }])
  91. ->where(['w.status' => 1, 'w.mall_id' => $mall_id])
  92. ->andWhere(['>', 'weight', 0])
  93. ->sum('uw.score');
  94. //平台符合条件的用户
  95. $all_user_score_arr = ScoreBonusUserWeight::find()
  96. ->alias('w')
  97. ->joinWith(['userWallet uw' => function($query) {
  98. $query->andWhere(['>', 'uw.score', 0]);
  99. }])
  100. ->where(['w.status' => 1, 'w.mall_id' => $mall_id])
  101. ->andWhere(['>', 'weight', 0])
  102. ->asArray()
  103. ->all();
  104. } else {
  105. //原模式 积分占比分配
  106. // 因为之前查询条件放在下面导致用户查询没有 到达积分条件限制。所以移动到此处
  107. $where = ['and', ['mall_id' => $mall_id], ['status' => StatusEnum::ENABLED], ['>=', 'score', $config['user_score']]];
  108. //平台符合条件的用户数据
  109. $all_user_score_arr = UserWallet::find()->where($where)->andWhere(['>','score',0])->asArray()->all();
  110. //平台符合条件用户的总积分
  111. $all_user_score = UserWallet::find()->where($where)->sum('score');
  112. }
  113. $all_user_score_num = count($all_user_score_arr);
  114. $i = 0;
  115. //插入钱包数据
  116. $insert_wallet = [];
  117. $settle_circle = ScoreBonusEnums::getSettleCircleMap();
  118. //写入当期结算数据写入结算周期表
  119. $settle_circle_data = [ //当期结算的数据
  120. 'mall_id' => $mall_id,
  121. 'bonus_circle' => $config['settle_circle'],
  122. 'status' => StatusEnum::DISABLED,
  123. 'num' => $check_score_bonus_data['num'] ? $check_score_bonus_data['num'] +1 : 1,
  124. 'bonus_price' => $capital_pool, //资金分红池总额
  125. 'bonus_num' => $all_user_score_num, //参与分红人数
  126. 'settle_price' => round($capital_pool, 2),//当期结算总金额
  127. 'total_score' => $all_user_score, //周期总积分
  128. 'date_time' => $date_time,
  129. 'settle_method' => $config['settle_method'] ?? 0, // 设置没有保存是配置默认为积分占比
  130. 'total_weight' => $all_user_weight ?? 0,
  131. ];
  132. if ($settle_circle_data['num'] < 10) {
  133. $settle_circle_data['num_text'] = $settle_circle_data['date_time'].'0'.$settle_circle_data['num'].'期';
  134. } else {
  135. $settle_circle_data['num_text'] = $settle_circle_data['date_time'].$settle_circle_data['num'].'期';
  136. }
  137. $res = ScoreBonusModel::setData($settle_circle_data);
  138. if (!$res) throw new \Exception($settle_circle_data['num_text'] . '写入结算周期数据失败' . ScoreBonusModel::$static_error);
  139. if ($capital_pool <= 0) throw new \Exception($settle_circle_data['num_text'] . '资金池为空');
  140. $bid = $res->id;
  141. $error_log = [
  142. 'mall_id' => $mall_id,
  143. 'bid' => $bid,
  144. 'date_time' => $date_time,
  145. ];
  146. // $trans = Yii::$app->db->beginTransaction();
  147. if (count($all_user_score_arr) == 0) throw new \Exception($settle_circle_data['num_text'] . '参与活动总人数为0,终止结算');
  148. $bonus_price = 0;
  149. //获取资产积分自定义名称
  150. $mall_config = Yii::$app->services->setting->mall->get($mall_id, 'score');
  151. $custom_score_name = !empty($mall_config['score_name']) ? $mall_config['score_name'] . '分红' : '积分分红';
  152. //处理用户数据
  153. while (!empty($all_user_score_arr[$i])) {
  154. if ($config['settle_method']) {
  155. //新模式 权重占比分配
  156. $commission = $capital_pool / $all_user_weight * $all_user_score_arr[$i]['weight'];
  157. $reward_log_data_score = $all_user_score_arr[$i]['userWallet']['score'];
  158. $all_user_score_arr[$i]['score'] = $all_user_score_arr[$i]['userWallet']['score'];
  159. $desc_text = '权重分红(';
  160. } else {
  161. //原模式 积分占比分配
  162. $commission = ($all_user_score_arr[$i]['score'] / $all_user_score) * $capital_pool;
  163. $reward_log_data_score = $all_user_score_arr[$i]['score'];
  164. $desc_text = $custom_score_name.'(';
  165. }
  166. if (empty($config['is_all_reward']) || $config['is_all_reward'] == 1) {
  167. if ($commission < 0.01) $commission = 0.01;
  168. } else {
  169. if ($commission < 0.01) {
  170. $settle_circle_data['bonus_num'] -= 1;
  171. continue;
  172. }
  173. if ($commission >= $all_user_score_arr[$i]['score']) $commission = $all_user_score_arr[$i]['score'];
  174. }
  175. $commission = round($commission, 2);
  176. $bonus_price += $commission;
  177. $reward_log_data = [
  178. 'mall_id' => $mall_id,
  179. 'user_id' => $all_user_score_arr[$i]['user_id'],
  180. 'bid' => $bid,
  181. 'amounts' => $capital_pool,
  182. 'total_score' => $all_user_score, //周期总积分
  183. 'score' => $reward_log_data_score,
  184. 'weight' => $all_user_score_arr[$i]['weight'] ?? 0,
  185. 'commission' => $commission ,
  186. 'status' => StatusEnum::ENABLED,
  187. 'bonus_circle' => $config['settle_circle'],
  188. 'date_time' => $date_time,
  189. ];
  190. if ($reward_log_data['commission'] >= 0) {
  191. $reward_log_data['change_type'] = 'add';
  192. } else {
  193. $reward_log_data['change_type'] = 'sub';
  194. }
  195. $result = ScoreBonusRewardLogModel::setData($reward_log_data);
  196. if (!$result) {
  197. $error_log['json_data'] = json_encode($reward_log_data);
  198. $error_log['error_log'] = "写入用户结算数据失败";
  199. ScoreBonusErrorLogModel::setData($error_log);
  200. continue;
  201. };
  202. //处理写入用户钱包数据
  203. $insert_wallet[] = [
  204. 'mall_id' => $mall_id,
  205. 'user_id' => $all_user_score_arr[$i]['user_id'],
  206. 'is_frozen' => false,
  207. 'wallet_type' => 'commission',
  208. 'money' => $commission, //分红金额
  209. 'desc' => $custom_score_name . '('.$settle_circle[$config['settle_circle']].')',
  210. 'source_table' => ScoreBonusModel::tableName(),
  211. 'source_table_id' => $bid,//结算周期id
  212. 'from_type' => UserWalletEnum::FROM_SCORE_BONUS,
  213. 'capital_type' => UserWalletEnum::BOUNS,
  214. ];
  215. $score = $commission;
  216. if($all_user_score_arr[$i]['score'] < $commission){
  217. $score = $all_user_score_arr[$i]['score'];
  218. }
  219. $insert_wallet[] = [
  220. 'mall_id' => $mall_id,
  221. 'user_id' => $all_user_score_arr[$i]['user_id'],
  222. 'is_frozen' => false,
  223. 'wallet_type' => 'score',
  224. 'money' => -$score, //分红金额
  225. 'desc' => $desc_text.$settle_circle[$config['settle_circle']].')',
  226. 'source_table' => ScoreBonusModel::tableName(),
  227. 'source_table_id' => $bid,//结算周期id
  228. 'from_type' => UserWalletEnum::FROM_SCORE_BONUS,
  229. 'capital_type' => UserWalletEnum::BOUNS,
  230. 'not_add_total' => true,
  231. ];
  232. //钱包数据不为空,请求异步修改用户钱包数据
  233. if ($i % 100 == 0 && $i > 0) { //每100条用户数据推送一次
  234. if (!empty($insert_wallet)) {
  235. $res = UserWalletService::batchHandleByQueue($insert_wallet);
  236. if (empty($res)) {
  237. $error_log['json_data'] = json_encode($insert_wallet);
  238. $error_log['error_log'] = '推送mq队列:' . UserWalletService::getStaticError();
  239. ScoreBonusErrorLogModel::setData($error_log);
  240. }
  241. $insert_wallet = [];
  242. }
  243. }
  244. //更新用户的累计收入
  245. $user_weight_info = ScoreBonusUserWeight::setCreateData([
  246. 'mall_id' => $mall_id,
  247. 'user_id' => $all_user_score_arr[$i]['user_id']
  248. ]);
  249. $user_weight_info->total_income += $commission;
  250. if (!$user_weight_info->save()) {
  251. $error_log['json_data'] = json_encode(['score_bonus_user_weight' => $user_weight_info]);
  252. $error_log['error_log'] = "修改积分分红用户权重数据失败";
  253. ScoreBonusErrorLogModel::setData($error_log);
  254. }
  255. $i++;
  256. }
  257. //检测是否还有用户数据未推送至钱包
  258. if (!empty($insert_wallet)) {
  259. $res = UserWalletService::batchHandleByQueue($insert_wallet);
  260. if (empty($res)) {
  261. $error_log['json_data'] = json_encode($insert_wallet);
  262. $error_log['error_log'] = '推送mq队列:' . UserWalletService::getStaticError();
  263. ScoreBonusErrorLogModel::setData($error_log);
  264. }
  265. $insert_wallet = [];
  266. }
  267. //处理资金池内数据
  268. // $capital_res = ScoreBonusCapitalModel::updateAll(['status' => StatusEnum::ENABLED], ['and', ['mall_id' => $mall_id], ['status' => StatusEnum::DISABLED], ['<=' ,'date_time', $date_time]]);
  269. if ($capital->bonus_price < $bonus_price) {
  270. $capital->total_price += $capital->bonus_price;
  271. $capital->bonus_price = 0;
  272. } else {
  273. $capital->total_price += $bonus_price;
  274. $capital->bonus_price = bcsub($capital->bonus_price, $bonus_price, 2);
  275. }
  276. if (!$capital->save()) {
  277. $error_log['json_data'] = json_encode(['bonus_price' => $bonus_price]);
  278. $error_log['error_log'] = "修改资金池数据失败";
  279. ScoreBonusErrorLogModel::setData($error_log);
  280. }
  281. //更新结算周期数据为已结算 并更新结算金额
  282. $res = ScoreBonusModel::updateAll(['status' => StatusEnum::ENABLED, 'settle_price' => $bonus_price], ['mall_id' => $mall_id, 'id' => $bid]);
  283. if (!$res) {
  284. $error_log['json_data'] = json_encode(['update_data' => ['status' => StatusEnum::ENABLED], 'where' => ['mall_id' => $mall_id, 'id' => $bid]]);
  285. $error_log['error_log'] = "更新结算周期数据失败";
  286. ScoreBonusErrorLogModel::setData($error_log);
  287. };
  288. // $trans->commit();
  289. return true;
  290. } catch (\Exception $e) {
  291. $error_log = [
  292. 'mall_id' => $mall_id,
  293. 'bid' => 0,
  294. 'date_time' => $date_time,
  295. 'error_log' => '商城:'.$mall_id.', scoreBonusSettle err:'.$e->getMessage(),
  296. 'json_data' => '',
  297. ];
  298. ScoreBonusErrorLogModel::setData($error_log);
  299. // $trans->rollBack();
  300. $exception = FormatHelper::exception($e,'商城:'.$mall_id.', scoreBonusSettle err:'.$e->getMessage());
  301. Yii::error($exception);
  302. return false;
  303. }
  304. }
  305. //迁移文件-累计分红池金额
  306. public function total_capital_pool($mall_id)
  307. {
  308. try {
  309. $config = Yii::$app->services->setting->addons->get($mall_id, ScoreBonusEnums::ADDONS_NAME, 'basic',true);
  310. $is_enable = $config['is_enable'] ?? StatusEnum::DISABLED;
  311. if(!$is_enable) return true;
  312. $capital_pool = ScoreBonusCapitalPool::getOne([
  313. ['mall_id' => $mall_id],
  314. ]);
  315. //初始化分红池
  316. if (empty($capital_pool)) {
  317. $capital_pool = new ScoreBonusCapitalPool();
  318. $capital_pool->loadDefaultValues();
  319. $capital_pool->mall_id = $mall_id;
  320. }
  321. //获取已结算订单
  322. $total_price = ScoreBonusCapitalModel::find()->where(['mall_id' => $mall_id, 'status' => StatusEnum::ENABLED])->sum('bonus_price');
  323. $total_price = round($total_price, 2);
  324. $capital_pool->total_price = $total_price;
  325. //获取未结算订单
  326. $bonus_price = ScoreBonusCapitalModel::find()->where(['mall_id' => $mall_id, 'status' => StatusEnum::DISABLED])->sum('bonus_price');
  327. $bonus_price = round($bonus_price, 2);
  328. $capital_pool->bonus_price = $bonus_price;
  329. if (!$capital_pool->save()) throw new Exception('修改分红池数据失败');
  330. ScoreBonusCapitalModel::updateAll(['status' => StatusEnum::ENABLED], ['mall_id' => $mall_id, 'status' => StatusEnum::DISABLED]);
  331. return true;
  332. } catch (\Exception $e) {
  333. throw new Exception($e->getMessage());
  334. }
  335. }
  336. }