TeamGradesPoolDetail.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. <?php
  2. namespace app\models;
  3. use yii\behaviors\TimestampBehavior;
  4. /**
  5. * This is the model class for table "{{%team_grades_pool_detail}}".
  6. *
  7. * @property integer $id
  8. * @property integer $pool_id
  9. * @property integer $user_id
  10. * @property float $amount
  11. * @property float $profit
  12. * @property float $price
  13. * @property integer $level
  14. * @property integer $is_send
  15. * @property integer $send_time
  16. * @property integer $is_delete
  17. * @property integer $created_at
  18. * @property integer $updated_at
  19. */
  20. class TeamGradesPoolDetail extends \yii\db\ActiveRecord
  21. {
  22. const SEND_STATUS_NO = 0;
  23. const SEND_STATUS_YES = 1;
  24. /**
  25. * {@inheritdoc}
  26. */
  27. public static function tableName()
  28. {
  29. return '{{%team_grades_pool_detail}}';
  30. }
  31. /**
  32. * @inheritdoc
  33. */
  34. public function rules()
  35. {
  36. return [
  37. [['id', 'user_id', 'is_send', 'send_time', 'created_at', 'updated_at', 'pool_id', 'is_delete', 'level'], 'integer'],
  38. [['amount', 'price', 'profit'], 'number']
  39. ];
  40. }
  41. public function behaviors()
  42. {
  43. return [
  44. [
  45. 'class' => TimestampBehavior::class
  46. ]
  47. ];
  48. }
  49. /**
  50. * @inheritdoc
  51. */
  52. public function attributeLabels()
  53. {
  54. return [
  55. 'id' => '',
  56. 'pool_id' => '奖金池id',
  57. 'user_id' => '用户id',
  58. 'amount' => '分红金额',
  59. 'profit' => '分红比例',
  60. 'price' => '分红实际金额',
  61. 'level' => '分红时候的等级',
  62. 'is_send' => '是否发放',
  63. 'send_time' => '发放时间',
  64. 'is_delete' => '',
  65. 'created_at' => '',
  66. 'updated_at' => '',
  67. ];
  68. }
  69. /**
  70. * 整体流程
  71. * 从下单用户开始往上级找 作为一条分红记录
  72. * 比如当前推荐关系为 A->B->C->D->E
  73. * 如果E下单了商品,则A、B、C、D、E均为分红团队成员则每个人的的业绩分红金额分别增加商品中设置的分红金额
  74. * 如果B的等级大于A的等级 则A的分红金额不增加
  75. *
  76. * 代码流程
  77. * 从下单人开始将下单用户作为开团队分红的一支记录 pool
  78. * 寻找下单人的上级 直到上级为0 将所有的上级放入 pool_detail,如果pool_detail存在且上级为团队分红人员则增加上级分红金额且增加记录pool_detail_ext
  79. * 如果记录已经存在pool_detail_ext 则不增加金额以及记录
  80. *
  81. * @param Order $order
  82. */
  83. public static function setPoolDetail($order) {
  84. debug_log(['开始分红记录'], 'team_grades_pool_detail.log');
  85. $t = \Yii::$app->db->beginTransaction();
  86. try {
  87. if ($order) {
  88. $user_id = $order->user_id;
  89. // $userTeamGrades = TeamGrades::getUserTeamGrades($user_id);
  90. // if (empty($userTeamGrades['id'])) {
  91. // throw new \Exception('该用户不是团队分红人员');
  92. // }
  93. $store_id = $order->store_id;
  94. $team_grades_setting = Option::get('team_grades_setting', $store_id, 'team_grades_setting')['value'];
  95. if (!empty($team_grades_setting)) {
  96. $team_grades_setting = json_decode($team_grades_setting, true);
  97. }
  98. if (empty($team_grades_setting)) {
  99. throw new \Exception('团队业绩分红设置未开启');
  100. }
  101. if (!intval($team_grades_setting['amount_rule_type'])) {
  102. // 获取商品业绩分红金额
  103. $amount = 0;
  104. $order_detail_goods_id = OrderDetail::find()->where(['order_id' => $order->id, 'is_delete' => 0])->select('goods_id, num')->asArray()->all();
  105. if (!empty($order_detail_goods_id)) {
  106. foreach ($order_detail_goods_id as $order_detail_item) {
  107. $order_detail_goods_amount = TeamGradesGoods::findOne(['goods_id' => $order_detail_item['goods_id'], 'is_delete' => 0])->goods_team_grades ?: 0;
  108. $amount = bcadd(bcmul($order_detail_goods_amount, $order_detail_item['num'], 2), $amount, 2);
  109. }
  110. }
  111. } else {
  112. $amount = $order->pay_price;
  113. }
  114. debug_log(['amount' => $amount], 'team_grades_pool_detail.log');
  115. if ($amount <= 0) {
  116. throw new \Exception('商品未设置业绩分红值');
  117. }
  118. // 是否过滤高等级
  119. $filter_high_level = intval($team_grades_setting['filter_high_level']);
  120. debug_log(['是否过滤高等级' => $filter_high_level], 'team_grades_pool_detail.log');
  121. /* begin 2025/04/27 11:48:36 当奖励模式为【级差模式】时,不过滤高等级 WPing丶 */
  122. if($team_grades_setting['bonus_type'] == 1) {
  123. $filter_high_level = 0;
  124. }
  125. /* end */
  126. // 获取未发放的奖金池
  127. $pool = TeamGradesPool::getPool($store_id, $user_id, $team_grades_setting['bonus_type']);
  128. debug_log(['获取未发放的奖金池' => $pool], 'team_grades_pool_detail.log');
  129. if (!$pool) {
  130. throw new \Exception('未找到未发放的奖金池');
  131. }
  132. // $pool_detail = self::findOne(['order_id' => $order->id, 'pool_id' => $pool['id'], 'store_id' => $store_id]);
  133. // debug_log(['该订单已存在该奖金池明细' => $pool_detail], 'team_grades_pool_detail.log');
  134. // if ($pool_detail) {
  135. // throw new \Exception('该订单已存在该奖金池明细');
  136. // }
  137. // $user_list = self::getParentTeam($user_id, $user_id, $store_id);
  138. $user_list = OldUserTreePath::find()->alias('ut')
  139. ->where(['ut.child_id' => $user_id])
  140. ->andWhere('ut.child_id != ut.parent_id')
  141. ->orderBy(['ut.parent_level' => SORT_DESC])
  142. ->select(['ut.*'])
  143. ->asArray()
  144. ->all();
  145. debug_log(['user_list' => $user_list], 'team_grades_pool_detail.log');
  146. //向奖金池记录增加记录
  147. // if (!empty($user_list)) {
  148. // $user_list = array_column($user_list, 'parent_id');
  149. // }
  150. array_unshift($user_list, [
  151. 'parent_id' => $user_id,
  152. 'parent_level' => 0
  153. ]);
  154. foreach ($user_list as $user_index => &$user_item) {
  155. $user_item['parent_level'] = $user_index;
  156. }
  157. unset($user_item);
  158. $team_grades_level = 0;
  159. foreach ($user_list as $user_item) {
  160. $init_amount = $amount;
  161. $userTeamGrades = TeamGrades::getUserTeamGrades($user_item['parent_id']);
  162. if (!empty($userTeamGrades['id'])) {
  163. if ($filter_high_level) {
  164. if ($team_grades_level < $userTeamGrades['team_grades_level']) {
  165. $team_grades_level = $userTeamGrades['team_grades_level'];
  166. } else {
  167. $init_amount = 0;
  168. }
  169. }
  170. } else {
  171. // $init_amount = 0;//如果该用户不是团队业绩分红成员就将业绩改成0
  172. continue;//新要求:这里只显示 有分红权限的用户 。所以直接给跳出循环
  173. }
  174. // 判断是否在团队范围内 不再范围内业绩就改成0
  175. if ($user_item['parent_level'] < $team_grades_setting['team_start_num'] || $user_item['parent_level'] > $team_grades_setting['team_end_num']) {
  176. $init_amount = 0;
  177. }
  178. debug_log(['init_amount' => $init_amount], 'team_grades_pool_detail.log');
  179. // 向明细表增加记录
  180. $pool_detail = self::findOne(['user_id' => $user_item['parent_id'], 'pool_id' => $pool['id']]) ?: new self();
  181. $pool_detail->pool_id = $pool['id'];
  182. $pool_detail->user_id = $user_item['parent_id'];
  183. // $pool_detail->amount = bcadd($pool_detail->amount, $init_amount);
  184. if (!$pool_detail->save()) {
  185. throw new \Exception(json_encode($pool_detail->errors, JSON_UNESCAPED_UNICODE));
  186. }
  187. // 向明细记录明细表增加记录
  188. $pool_detail_ext = TeamGradesPoolDetailExt::findOne([
  189. 'order_id' => $order->id,
  190. 'pool_detail_id' => $pool_detail->id,
  191. 'user_id' => $user_item['parent_id'],
  192. ]);
  193. if ($pool_detail_ext) {
  194. continue;
  195. }
  196. $pool_detail_ext = new TeamGradesPoolDetailExt();
  197. $pool_detail_ext->goods_amount = $amount;
  198. $pool_detail_ext->amount = $init_amount;
  199. $pool_detail_ext->order_id = $order->id;
  200. $pool_detail_ext->pool_detail_id = $pool_detail->id;
  201. $pool_detail_ext->user_id = $user_item['parent_id'];
  202. if (!$pool_detail_ext->save()) {
  203. throw new \Exception(json_encode($pool_detail_ext->errors, JSON_UNESCAPED_UNICODE));
  204. }
  205. // 更新奖金池分红
  206. $pool_detail->amount = bcadd($pool_detail->amount, $init_amount, 2);
  207. if (!$pool_detail->save()) {
  208. throw new \Exception(json_encode($pool_detail->errors, JSON_UNESCAPED_UNICODE));
  209. }
  210. }
  211. $t->commit();
  212. return ;
  213. }
  214. throw new \Exception('订单不存在');
  215. } catch (\Exception $e) {
  216. debug_log(['getMessage' => $e->getMessage()], 'team_grades_pool_detail.log');
  217. $t->rollBack();
  218. return [
  219. 'code' => 1,
  220. 'msg' => $e->getMessage()
  221. ];
  222. }
  223. }
  224. /**
  225. * 发放奖金池根据用户团队分红等级内设置的佣金比例
  226. */
  227. public static function sendPool($pool_id)
  228. {
  229. $t = \Yii::$app->db->beginTransaction();
  230. try {
  231. // 获取未发放的奖金池
  232. $pool = TeamGradesPool::findOne(['id' => $pool_id, 'is_send' => 0]);
  233. if (!$pool) {
  234. throw new \Exception('奖金池不存在');
  235. }
  236. // 获取未发放的奖金池明细 准备发放 2025年4月28日09:59:18新增排序防止级差发放错乱
  237. $pool_detail_list = self::find()->alias('pd')
  238. ->leftJoin(['tg' => TeamGrades::tableName()], 'pd.user_id = tg.user_id')
  239. ->where(['pd.pool_id' => $pool_id, 'pd.is_send' => self::SEND_STATUS_NO])
  240. ->orderBy(['tg.team_grades_level' => SORT_ASC])
  241. ->asArray()->all();
  242. if (!empty($pool_detail_list)) {
  243. $last_tiered_bonus = 0;
  244. foreach ($pool_detail_list as $pool_detail_item) {
  245. $price = 0;
  246. $profit = 0;
  247. // 获取用户是否是团队分红成员且有团队等级
  248. $userTeamGrades = TeamGrades::getUserTeamGrades($pool_detail_item['user_id']);
  249. if (!empty($userTeamGrades['id'])) {
  250. /* begin 2025/04/27 14:22:43 WPing丶 新增级差模式调整后的代码,上方注释为源代码已兼容 */
  251. if($pool->bonus_type == 0) {//业绩阶梯模式
  252. $result = self::getPrice($pool_detail_item['amount'], $userTeamGrades['team_grades_level'], $pool->store_id);
  253. $price = $result['price'];
  254. $profit = $result['profit'];
  255. } elseif($pool->bonus_type == 1) {//级差模式
  256. $result = self::getPrice($pool_detail_item['amount'], $userTeamGrades['team_grades_level'], $pool->store_id, $last_tiered_bonus);
  257. $price = $result['price'];
  258. $profit = $result['profit'];
  259. $last_tiered_bonus = $result['tiered_bonus'];
  260. } else {
  261. throw new \Exception('奖励类型【'.$pool->bonus_type.'】不是期望值');
  262. }
  263. /* end */
  264. }
  265. $userPoolDetail = self::findOne($pool_detail_item['id']);
  266. $userPoolDetail->price = $price;
  267. $userPoolDetail->profit = $profit;
  268. $userPoolDetail->is_send = self::SEND_STATUS_YES;
  269. $userPoolDetail->send_time = time();
  270. $userPoolDetail->level = $userTeamGrades['team_grades_level'];
  271. if (!$userPoolDetail->save()) {
  272. throw new \Exception(json_encode($userPoolDetail->errors, JSON_UNESCAPED_UNICODE));
  273. }
  274. if ($price > 0) {
  275. //更新团队分红累计金额
  276. $userTeamGrades_ = TeamGrades::findOne($userTeamGrades['id']);
  277. $userTeamGrades_->price = bcadd($userTeamGrades_->price, $price, 2);
  278. $userTeamGrades_->total_price = bcadd($userTeamGrades_->total_price, $price, 2);
  279. if (!$userTeamGrades_->save()) {
  280. throw new \Exception(json_encode($userTeamGrades_->errors, JSON_UNESCAPED_UNICODE));
  281. }
  282. //判断用户是否存在 存在则开始发放
  283. $user = User::findOne(['id' => $pool_detail_item['user_id'], 'is_delete' => 0]);
  284. if ($user) {
  285. $user->price = bcadd($user->price, $price, 2);
  286. $user->total_price = bcadd($user->total_price, $price, 2);
  287. if (!$user->save()) {
  288. throw new \Exception(json_encode($user->errors, JSON_UNESCAPED_UNICODE));
  289. }
  290. $result = UserShareMoney::set($price, $pool_detail_item['user_id'], 0, 0, 11, $pool->store_id, 0, '团队业绩分红');
  291. if (!$result) {
  292. throw new \Exception('保存失败');
  293. }
  294. }
  295. }
  296. }
  297. }
  298. //如果所有明细发放完毕 则将奖金池状态设置为已发放
  299. $pool_detail_list = self::find()->where(['pool_id' => $pool_id, 'is_send' => self::SEND_STATUS_NO])->asArray()->all();
  300. if (empty($pool_detail_list)) {
  301. $pool->is_send = self::SEND_STATUS_YES;
  302. $pool->send_time = time();
  303. if (!$pool->save()) {
  304. throw new \Exception(json_encode($pool->errors, JSON_UNESCAPED_UNICODE));
  305. }
  306. }
  307. $t->commit();
  308. return [
  309. 'code' => 0,
  310. 'msg' => '操作成功'
  311. ];
  312. } catch (\Exception $e) {
  313. $t->rollBack();
  314. return [
  315. 'code' => 1,
  316. 'msg' => $e->getMessage()
  317. ];
  318. }
  319. }
  320. //通过业绩金额 计算分红金额
  321. public static function getPrice($amount, $team_grades_level, $store_id, $last_profit = 0)
  322. {
  323. try {
  324. $price = 0;
  325. $profit = 0;
  326. $userTeamGradesLevel = TeamGradesLevel::getLevelInfo($team_grades_level, $store_id);
  327. $team_grades_setting = Option::get('team_grades_setting', $store_id, 'team_grades_setting')['value'];
  328. $team_grades_setting = json_decode($team_grades_setting, true);
  329. $bonus_type = $team_grades_setting['bonus_type'];
  330. if ($userTeamGradesLevel) {
  331. if($bonus_type == 0) {
  332. if ($userTeamGradesLevel->level_reward_setting) {
  333. $level_reward_setting = json_decode($userTeamGradesLevel->level_reward_setting, true);
  334. $max_level_reward = [];
  335. $open = true;
  336. //遍历循环奖励 查询业绩金额是否符合条件
  337. if (!empty($level_reward_setting)) {
  338. foreach ($level_reward_setting as $level_reward_item) {
  339. if (isset($level_reward_item['profit']) && isset($level_reward_item['min']) && isset($level_reward_item['max'])) {
  340. //如果业绩条件成立 就开始使用成立条件对应的佣金比例 乘以 业绩金额 算出来实际到账佣金
  341. if ($amount >= $level_reward_item['min'] && $amount <= $level_reward_item['max']) {
  342. // 计算出实际到账佣金
  343. $price = bcmul($amount, bcdiv($level_reward_item['profit'], 100, 2), 2);
  344. $profit = $level_reward_item['profit'];
  345. $open = false;
  346. break;
  347. }
  348. //拿到最大的业绩区间
  349. if (empty($max_level_reward) || $max_level_reward['max'] < $level_reward_item['max']) {
  350. $max_level_reward = $level_reward_item;
  351. }
  352. }
  353. }
  354. }
  355. //如果不在以上业绩区间内 就用最大的业绩区间的比例去计算实际到账佣金
  356. if ($open && !empty($max_level_reward) && $amount > $max_level_reward['max']) {
  357. $price = bcmul($amount, bcdiv($max_level_reward['profit'], 100, 2), 2);
  358. $profit = $max_level_reward['profit'];
  359. }
  360. }
  361. } elseif ($bonus_type == 1) {// 级差模式
  362. $profit = bcsub($userTeamGradesLevel->tiered_bonus, $last_profit, 2);
  363. $price = bcmul($amount, bcdiv($profit, 100, 2), 2);
  364. $tiered_bonus = $userTeamGradesLevel->tiered_bonus;
  365. if($profit <= 0) {
  366. $profit = 0;
  367. $price = 0;
  368. $tiered_bonus = 0;
  369. }
  370. } else {
  371. throw new \Exception('奖励类型【'.$pool->bonus_type.'】不是期望值');
  372. }
  373. }
  374. } catch (\Exception $e) {
  375. debug_log([
  376. 'line' => $e->getLine(),
  377. 'msg' => $e->getMessage(),
  378. 'file' => $e->getFile()
  379. ], 'TeamGradesPoolDetail.log');
  380. }
  381. return [
  382. 'price' => $price,
  383. 'profit' => $profit,
  384. 'tiered_bonus' => $tiered_bonus?:0,
  385. ];
  386. }
  387. }