Bläddra i källkod

feat(shared-prosperity): 添加共富体系团队奖励分配功能

- 新增共富体系团队奖励分配逻辑(按团队平均分配)
- 实现测试模式支持,可模拟计算不实际发放
- 添加分配明细数据收集和返回功能
- 集成团队配置和用户分组管理
- 优化原有合伙人分配算法,支持精确计算
- 添加事务控制和异常处理机制
- 实现分配权重计算和日志记录功能
shichen 5 månader sedan
förälder
incheckning
7dac6d8d8a
1 ändrade filer med 298 tillägg och 13 borttagningar
  1. 298 13
      app/common/repositories/shared/SharedProsperityTaskRepository.php

+ 298 - 13
app/common/repositories/shared/SharedProsperityTaskRepository.php

@@ -3,13 +3,18 @@
3 3
 namespace app\common\repositories\shared;
4 4
 
5 5
 use app\common\dao\shared\SharedProsperityBalanceLogDao;
6
+use app\common\dao\shared\SharedProsperityGroupDao;
7
+use app\common\dao\shared\SharedProsperityGroupUserDao;
6 8
 use app\common\dao\shared\SharedProsperityPointLogDao;
7 9
 use app\common\dao\shared\SharedProsperityRecommendConfigDao;
8 10
 use app\common\dao\shared\SharedProsperityUserDao;
9 11
 use app\common\enum\CommonEnum;
12
+use app\common\enum\dividend\DividendLogEnum;
13
+use app\common\enum\shared\SharedProsperityBalanceLogEnum;
10 14
 use app\common\enum\shared\SharedProsperityRewardRecordEnum;
11 15
 use app\common\repositories\BaseRepository;
12 16
 use app\common\repositories\user\UserBillRepository;
17
+use app\entity\data\dividend\DividendLogEntity;
13 18
 use think\facade\Db;
14 19
 use think\facade\Log;
15 20
 
@@ -19,9 +24,27 @@ class SharedProsperityTaskRepository extends BaseRepository
19 24
 
20 25
     const REWARD_TYPE_PV = SharedProsperityRewardRecordEnum::TYPE['PV']['code'];
21 26
 
27
+    // 临时分配规则
28
+    const CONFIG = [
29
+        // 用户
30
+        0 => "1.00",
31
+        // 一星合伙人
32
+        1 => "10.00",
33
+        // 二星合伙人
34
+        2 => "8.00",
35
+        // 三星合伙人
36
+        3 => "5.00",
37
+        // 钻石合伙人
38
+        4 => "3.00",
39
+        // 总裁合伙人
40
+        5 => "2.00",
41
+        // 联席主席团
42
+        6 => "1.00"
43
+    ];
44
+
22 45
     /**
23 46
      * @param $data
24
-     * @return void
47
+     * @return array 分配明细数据
25 48
      * @throws \Exception
26 49
      * @author 史晨
27 50
      * @date 2026/2/10 10:10
@@ -30,9 +53,9 @@ class SharedProsperityTaskRepository extends BaseRepository
30 53
     public function partner($data)
31 54
     {
32 55
         // 获取时间范围内订单
33
-        $merId = CommonEnum::DESIGN_MERCHANT_ID['SharedProsperity']['code'];
34 56
         $start = $data['startTime'];
35 57
         $end = $data['endTime'];
58
+        $isTest = $data['isTest'] ?? false; // 是否测试模式(不实际发放)
36 59
 
37 60
         // 获取pv
38 61
         /** @var SharedProsperityRewardRecordRepository $rewardRecordRepository */
@@ -46,10 +69,13 @@ class SharedProsperityTaskRepository extends BaseRepository
46 69
         $conPri = floor($totalConPri * self::PARTNER_RADIO * 100) / 100;
47 70
 
48 71
         // 获取合伙人分配规则
49
-        /** @var SharedProsperityRecommendConfigDao $configDao */
50
-        $configDao = app()->make(SharedProsperityRecommendConfigDao::class);
51
-        $config = $configDao->getConfigList(false);
52
-        $config = array_column($config, 'profit_ratio', 'level');
72
+        $config = self::CONFIG;
73
+        if (empty($config)) {
74
+            /** @var SharedProsperityRecommendConfigDao $configDao */
75
+            $configDao = app()->make(SharedProsperityRecommendConfigDao::class);
76
+            $config = $configDao->getConfigList(false);
77
+            $config = array_column($config, 'profit_ratio', 'level');
78
+        }
53 79
         if (empty($config)) {
54 80
             throw new \Exception('没有合伙人分配规则');
55 81
         }
@@ -61,7 +87,14 @@ class SharedProsperityTaskRepository extends BaseRepository
61 87
         /** @var SharedProsperityBalanceLogDao $balanceDao */
62 88
         $balanceDao = app()->make(SharedProsperityBalanceLogDao::class);
63 89
 
64
-        Db::startTrans();
90
+        // 分配明细数据收集
91
+        $distributionDetails = [];
92
+
93
+        // 如果是测试模式,不开启事务,只计算不实际发放
94
+        if (!$isTest) {
95
+            Db::startTrans();
96
+        }
97
+
65 98
         try {
66 99
             foreach ($users as $key => $userIds) {
67 100
                 if ($key == 0) {
@@ -69,7 +102,7 @@ class SharedProsperityTaskRepository extends BaseRepository
69 102
                 }
70 103
                 $radio = $config[$key];
71 104
                 // 该等级总分红
72
-                $levelTotal = floor(($conPri * ($radio / 100)) * 100) / 100;
105
+                $levelTotal = bcmul($conPri, bcdiv($radio, 100, 2), 2);
73 106
 
74 107
                 // 获取该等级所有用户的基本信息
75 108
                 $userInfos = $userDao->getSharedProsperityUserEntityListByUserIdList($userIds);
@@ -91,20 +124,58 @@ class SharedProsperityTaskRepository extends BaseRepository
91 124
                     $eachReward = floor(($levelTotal / $userNum) * 100) / 100;
92 125
                     foreach ($userInfos as $userInfo) {
93 126
                         $userReward = $eachReward;
94
-                        $this->distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao);
127
+                        $weight = 1 / $userNum; // 平均分配的权重
128
+                        // 收集分配明细
129
+                        $distributionDetails[] = [
130
+                            'user_id' => $userInfo->getUserId(), //用户ID
131
+                            'level' => $key, //合伙人等级
132
+                            'total_allocation' => $levelTotal, //该等级总分配金额
133
+                            'team_pv' => $teamPvMap[$userInfo->getUserId()] ?? 0, //个人团队PV
134
+                            'current_level_team_pv' => $totalTeamPv, //当前等级所有用户的团队PV总和
135
+                            'ratio' => $weight, //占比(个人团队PV/当前等级团队PV)
136
+                            'actual_amount' => $userReward,//实际发放金额
137
+                        ];
138
+                        $this->distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao, $isTest);
95 139
                     }
96 140
                 } else {
97 141
                     // 加权分配
98 142
                     foreach ($userInfos as $userInfo) {
99 143
                         $weight = $teamPvMap[$userInfo->getUserId()] / $totalTeamPv;
100 144
                         $userReward = floor($levelTotal * $weight * 100) / 100;
101
-                        $this->distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao);
145
+                        // 收集分配明细
146
+                        $distributionDetails[] = [
147
+                            'user_id' => $userInfo->getUserId(),//用户ID
148
+                            'level' => $key,//合伙人等级
149
+                            'total_allocation' => $levelTotal,//该等级总分配金额
150
+                            'team_pv' => $teamPvMap[$userInfo->getUserId()] ?? 0,//个人团队PV
151
+                            'current_level_team_pv' => $totalTeamPv,//当前等级所有用户的团队PV总和
152
+                            'ratio' => $weight,//占比(个人团队PV/当前等级团队PV)
153
+                            'actual_amount' => $userReward,//实际发放金额
154
+                        ];
155
+                        $this->distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao, $isTest);
102 156
                     }
103 157
                 }
104 158
             }
105
-            Db::commit();
159
+
160
+            if (!$isTest) {
161
+                // 记录分红日志
162
+                // $this->dividendLog($start, $end, $conPri, $conPri, $config, $isTest, '共富体系合伙人分红');
163
+                Db::commit();
164
+            }
165
+
166
+            // 返回分配明细数据
167
+            return [
168
+                'success' => true,
169
+                'is_test' => $isTest,
170
+                'total_pv' => $totalConPri,
171
+                'allocatable_profit' => $conPri,
172
+                'distribution_details' => $distributionDetails,
173
+                'config' => $config,
174
+            ];
106 175
         } catch (\Exception $e) {
107
-            Db::rollback();
176
+            if (!$isTest) {
177
+                Db::rollback();
178
+            }
108 179
             throw new \Exception($e->getMessage());
109 180
         }
110 181
     }
@@ -117,11 +188,19 @@ class SharedProsperityTaskRepository extends BaseRepository
117 188
      * @param $levelTotal
118 189
      * @param $balanceDao
119 190
      * @param $userDao
191
+     * @param bool $isTest 是否测试模式(不实际发放)
120 192
      * @return void
121 193
      */
122
-    private function distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao)
194
+    private function distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao, $isTest = false)
123 195
     {
124 196
         $userId = $userInfo->getUserId();
197
+
198
+        // 如果是测试模式,只记录日志不实际执行数据库操作
199
+        if ($isTest) {
200
+            Log::info("测试模式:用户 {$userId} 应发放奖励 {$userReward},等级 {$userInfo->getPartnerLevel()},比例 {$radio},等级总分红 {$levelTotal}");
201
+            return;
202
+        }
203
+
125 204
         $balanceDao->create([
126 205
             'user_id' => $userId,
127 206
             'level' => $userInfo->getPartnerLevel(),
@@ -163,6 +242,23 @@ class SharedProsperityTaskRepository extends BaseRepository
163 242
         $userDao->updateByUserId($userId, ['balance' => ['inc', $userReward]]);
164 243
     }
165 244
 
245
+    // private function dividendLog($startTime, $endTime, $performance, $allocated, $starList, $isTest, $remark)
246
+    // {
247
+    //     // 组装日志信息
248
+    //     /** @var DividendLogEntity $dividendLogEntity */
249
+    //     $dividendLogEntity = DividendLogEntity::newInstance();
250
+    //     $dividendLogEntity->setType(DividendLogEnum::TYPE['SharedProsperityPartnerProfit']['code'])
251
+    //         ->setStartTime($startTime)
252
+    //         ->setEndTime($endTime)
253
+    //         ->setPerformance($performance)
254
+    //         ->setAbatement(json_encode([]))
255
+    //         ->setAllocated($allocated)
256
+    //         ->setRule(json_encode($starList))
257
+    //         ->setIsTest($isTest)
258
+    //         ->setRemark($remark)
259
+    //         ->setExpected();
260
+    // }
261
+
166 262
     /**
167 263
      * @param $data
168 264
      * @return true
@@ -264,4 +360,193 @@ class SharedProsperityTaskRepository extends BaseRepository
264 360
             throw new \Exception($e->getMessage());
265 361
         }
266 362
     }
363
+
364
+    /**
365
+     * @param $data
366
+     * @return array 分配明细数据
367
+     * @throws \Exception
368
+     * @author 史晨
369
+     * @date 2026/2/14 10:54
370
+     * 共富奖(团队平均分配)
371
+     */
372
+    public function group($data)
373
+    {
374
+        // 获取时间范围内订单
375
+        $start = $data['startTime'];
376
+        $end = $data['endTime'];
377
+        $isTest = $data['isTest'] ?? false; // 是否测试模式(不实际发放)
378
+
379
+        // 获取pv
380
+        /** @var SharedProsperityRewardRecordRepository $rewardRecordRepository */
381
+        $rewardRecordRepository = app()->make(SharedProsperityRewardRecordRepository::class);
382
+        $orderList = $rewardRecordRepository->getListByTime($start, $end, self::REWARD_TYPE_PV);
383
+        if (empty($orderList)) {
384
+            throw new \Exception('没有订单');
385
+        }
386
+        // 计算总PV
387
+        $totalPv = array_sum(array_column($orderList, 'number'));
388
+        $totalPv = 100;
389
+        // 可分配总额 = 总PV * 15%
390
+        $totalAllocatable = floor($totalPv * 0.15 * 100) / 100;
391
+
392
+        // 获取团队配置
393
+        /** @var SharedProsperityGroupDao $groupDao */
394
+        $groupDao = app()->make(SharedProsperityGroupDao::class);
395
+        $groups = $groupDao->getAllGroups(); // 获取所有团队等级
396
+        if (empty($groups)) {
397
+            throw new \Exception('没有团队配置');
398
+        }
399
+
400
+        // 按 group_id 映射比例
401
+        $groupRatios = [];
402
+        foreach ($groups as $group) {
403
+            $groupRatios[$group['id']] = [
404
+                'name' => $group['name'],
405
+                'radio' => $group['radio'], // 百分比,例如 8.00
406
+                'max' => $group['max']
407
+            ];
408
+        }
409
+
410
+        // 获取团队成员
411
+        /** @var SharedProsperityGroupUserDao $groupUserDao */
412
+        $groupUserDao = app()->make(SharedProsperityGroupUserDao::class);
413
+        $groupUsers = $groupUserDao->getValidUsersGroupByGroup(); // 获取有效用户按团队分组
414
+        if (empty($groupUsers)) {
415
+            throw new \Exception('没有团队成员');
416
+        }
417
+
418
+        /** @var SharedProsperityBalanceLogDao $balanceDao */
419
+        $balanceDao = app()->make(SharedProsperityBalanceLogDao::class);
420
+        /** @var SharedProsperityUserDao $userDao */
421
+        $userDao = app()->make(SharedProsperityUserDao::class);
422
+
423
+        // 分配明细数据收集
424
+        $distributionDetails = [];
425
+
426
+        // 如果是测试模式,不开启事务,只计算不实际发放
427
+        if ($isTest === 'false') {
428
+            Db::startTrans();
429
+        }
430
+
431
+        try {
432
+            foreach ($groupRatios as $groupId => $groupInfo) {
433
+                $ratio = $groupInfo['radio']; // 百分比
434
+                // 该团队等级总分配金额 = 总PV * ratio / 100
435
+                $groupTotal = floor($totalPv * $ratio / 100 * 100) / 100;
436
+                // 获取该团队等级下的有效用户
437
+                $userIds = $groupUsers[$groupId] ?? [];
438
+                if (empty($userIds)) {
439
+                    // 没有成员,跳过分配
440
+                    continue;
441
+                }
442
+                $userCount = count($userIds);
443
+                // 平均分配
444
+                $eachReward = floor(($groupTotal / $userCount) * 100) / 100;
445
+                // 获取用户信息
446
+                $userInfos = $userDao->getSharedProsperityUserEntityListByUserIdList($userIds);
447
+
448
+                if (empty($userInfos)) {
449
+                    continue;
450
+                }
451
+                foreach ($userInfos as $userInfo) {
452
+                    $userReward = $eachReward;
453
+                    // 收集分配明细
454
+                    $distributionDetails[] = [
455
+                        'user_id' => $userInfo->getUserId(),
456
+                        'group_id' => $groupId,
457
+                        'group_name' => $groupInfo['name'],
458
+                        'total_allocation' => $groupTotal,
459
+                        'user_count' => $userCount,
460
+                        'ratio' => $ratio,
461
+                        'actual_amount' => $userReward,
462
+                    ];
463
+                    $this->distributeGroupReward($userInfo, $userReward, $ratio, $groupTotal, $balanceDao, $userDao, $groupId, $isTest);
464
+                }
465
+            }
466
+
467
+            if ($isTest === 'false') {
468
+                // 记录分红日志
469
+                // $this->dividendLog($start, $end, $totalPv, $totalAllocatable, $groupRatios, $isTest, '共富体系团队奖');
470
+                Db::commit();
471
+            }
472
+
473
+            // 返回分配明细数据
474
+            return [
475
+                'success' => true,
476
+                'is_test' => $isTest,
477
+                'total_pv' => $totalPv,
478
+                'allocatable_profit' => $totalAllocatable,
479
+                'distribution_details' => $distributionDetails,
480
+                'group_ratios' => $groupRatios,
481
+            ];
482
+        } catch (\Exception $e) {
483
+            if ($isTest === 'false') {
484
+                Db::rollback();
485
+            }
486
+            throw new \Exception($e->getMessage());
487
+        }
488
+    }
489
+
490
+    /**
491
+     * 分发团队奖励(记录日志和更新余额)
492
+     * @param $userInfo
493
+     * @param $userReward
494
+     * @param $radio
495
+     * @param $groupTotal
496
+     * @param $balanceDao
497
+     * @param $userDao
498
+     * @param $isTest
499
+     * @return void
500
+     */
501
+    private function distributeGroupReward($userInfo, $userReward, $radio, $groupTotal, $balanceDao, $userDao, $groupId, $isTest)
502
+    {
503
+        $userId = $userInfo->getUserId();
504
+
505
+        // 如果是测试模式,只记录日志不实际执行数据库操作
506
+        if ($isTest === 'true') {
507
+            Log::info("测试模式:用户 {$userId} 应发放团队奖励 {$userReward},比例 {$radio},团队总分红 {$groupTotal}");
508
+            return;
509
+        }
510
+
511
+        $balanceDao->create([
512
+            'user_id' => $userId,
513
+            'level' => $groupId, // 共富团队等级
514
+            'radio' => $radio,
515
+            'total_profit' => $groupTotal,
516
+            'old' => $userInfo->getBalance(),
517
+            'number' => $userReward,
518
+            'after' => $userInfo->getBalance() + $userReward,
519
+            'type' => SharedProsperityBalanceLogEnum::TYPE['GROUP']['code']
520
+        ]);
521
+
522
+        $bill = [
523
+            'uid' => $userId,
524
+            'link_id' => 0,
525
+            'pm' => 1,
526
+            'title' => '共富体系团队奖',
527
+            'category' => 'now_money',
528
+            'type' => 'commission',
529
+            'number' => $userReward,
530
+            'balance' => 0,
531
+            'mark' => '共富体系团队奖明细入账',
532
+            'create_time' => date('Y-m-d H:i:s'),
533
+            'status' => 1,
534
+            'commission_type' => CommonEnum::COMMISSION_TYPE['SHARED_PROSPERITY_GROUP_COMMISSION']['code'],
535
+            'order_sn' => 0,
536
+            'tripartite' => 0,
537
+            'type_shop' => 0,
538
+            'mer_id' => 0,
539
+            'source' => 0,
540
+            'order_type' => 1,
541
+            'is_red_brokerage' => 0,
542
+            'gzc' => 3,
543
+            'take_time' => time(),
544
+            'district_id' => '',
545
+            'street_id' => '',
546
+            'month' => '',
547
+        ];
548
+
549
+        Db::name('user_bill')->insert($bill);
550
+        $userDao->updateByUserId($userId, ['balance' => ['inc', $userReward]]);
551
+    }
267 552
 }