SharedProsperityTaskRepository.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <?php
  2. namespace app\common\repositories\shared;
  3. use app\common\dao\shared\SharedProsperityBalanceLogDao;
  4. use app\common\dao\shared\SharedProsperityGroupDao;
  5. use app\common\dao\shared\SharedProsperityGroupUserDao;
  6. use app\common\dao\shared\SharedProsperityPointLogDao;
  7. use app\common\dao\shared\SharedProsperityRecommendConfigDao;
  8. use app\common\dao\shared\SharedProsperityUserDao;
  9. use app\common\enum\CommonEnum;
  10. use app\common\enum\dividend\DividendLogEnum;
  11. use app\common\enum\shared\SharedProsperityBalanceLogEnum;
  12. use app\common\enum\shared\SharedProsperityRewardRecordEnum;
  13. use app\common\repositories\BaseRepository;
  14. use app\common\repositories\user\UserBillRepository;
  15. use app\entity\data\dividend\DividendLogEntity;
  16. use think\facade\Db;
  17. use think\facade\Log;
  18. class SharedProsperityTaskRepository extends BaseRepository
  19. {
  20. const PARTNER_RADIO = 1;
  21. const REWARD_TYPE_PV = SharedProsperityRewardRecordEnum::TYPE['PV']['code'];
  22. // 临时分配规则
  23. const CONFIG = [
  24. // 用户
  25. 0 => "1.00",
  26. // 一星合伙人
  27. 1 => "10.00",
  28. // 二星合伙人
  29. 2 => "8.00",
  30. // 三星合伙人
  31. 3 => "5.00",
  32. // 钻石合伙人
  33. 4 => "3.00",
  34. // 总裁合伙人
  35. 5 => "2.00",
  36. // 联席主席团
  37. 6 => "1.00"
  38. ];
  39. /**
  40. * @param $data
  41. * @return array 分配明细数据
  42. * @throws \Exception
  43. * @author 史晨
  44. * @date 2026/2/10 10:10
  45. * 合伙人分红(加权分配)
  46. */
  47. public function partner($data)
  48. {
  49. // 获取时间范围内订单
  50. $start = $data['startTime'];
  51. $end = $data['endTime'];
  52. $isTest = $data['isTest'] ?? false; // 是否测试模式(不实际发放)
  53. // 获取pv
  54. /** @var SharedProsperityRewardRecordRepository $rewardRecordRepository */
  55. $rewardRecordRepository = app()->make(SharedProsperityRewardRecordRepository::class);
  56. $orderList = $rewardRecordRepository->getListByTime($start, $end, self::REWARD_TYPE_PV);
  57. if (empty($orderList)) {
  58. throw new \Exception('没有订单');
  59. }
  60. // 计算可分配利润
  61. $totalConPri = array_sum(array_column($orderList, 'number'));
  62. $conPri = floor($totalConPri * self::PARTNER_RADIO * 100) / 100;
  63. // 获取合伙人分配规则
  64. $config = self::CONFIG;
  65. if (empty($config)) {
  66. /** @var SharedProsperityRecommendConfigDao $configDao */
  67. $configDao = app()->make(SharedProsperityRecommendConfigDao::class);
  68. $config = $configDao->getConfigList(false);
  69. $config = array_column($config, 'profit_ratio', 'level');
  70. }
  71. if (empty($config)) {
  72. throw new \Exception('没有合伙人分配规则');
  73. }
  74. // 获取合伙人
  75. /** @var SharedProsperityUserDao $userDao */
  76. $userDao = app()->make(SharedProsperityUserDao::class);
  77. $users = $userDao->getUserGroupByPartnerLevel();
  78. /** @var SharedProsperityBalanceLogDao $balanceDao */
  79. $balanceDao = app()->make(SharedProsperityBalanceLogDao::class);
  80. // 分配明细数据收集
  81. $distributionDetails = [];
  82. // 如果是测试模式,不开启事务,只计算不实际发放
  83. if (!$isTest) {
  84. Db::startTrans();
  85. }
  86. try {
  87. foreach ($users as $key => $userIds) {
  88. if ($key == 0) {
  89. continue;
  90. }
  91. $radio = $config[$key];
  92. // 该等级总分红
  93. $levelTotal = bcmul($conPri, bcdiv($radio, 100, 2), 2);
  94. // 获取该等级所有用户的基本信息
  95. $userInfos = $userDao->getSharedProsperityUserEntityListByUserIdList($userIds);
  96. if (empty($userInfos)) {
  97. continue;
  98. }
  99. // 计算每个用户的团队业绩(实时统计)
  100. $teamPvMap = [];
  101. $totalTeamPv = 0;
  102. foreach ($userInfos as $userInfo) {
  103. $teamPv = $userDao->getTeamPvSum($userInfo->getUserId());
  104. $teamPvMap[$userInfo->getUserId()] = $teamPv;
  105. $totalTeamPv += $teamPv;
  106. }
  107. // 如果总团队业绩为0,则平均分配
  108. if ($totalTeamPv == 0) {
  109. $userNum = count($userInfos);
  110. $eachReward = floor(($levelTotal / $userNum) * 100) / 100;
  111. foreach ($userInfos as $userInfo) {
  112. $userReward = $eachReward;
  113. $weight = 1 / $userNum; // 平均分配的权重
  114. // 收集分配明细
  115. $distributionDetails[] = [
  116. 'user_id' => $userInfo->getUserId(), //用户ID
  117. 'level' => $key, //合伙人等级
  118. 'total_allocation' => $levelTotal, //该等级总分配金额
  119. 'team_pv' => $teamPvMap[$userInfo->getUserId()] ?? 0, //个人团队PV
  120. 'current_level_team_pv' => $totalTeamPv, //当前等级所有用户的团队PV总和
  121. 'ratio' => $weight, //占比(个人团队PV/当前等级团队PV)
  122. 'actual_amount' => $userReward,//实际发放金额
  123. ];
  124. $this->distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao, $isTest);
  125. }
  126. } else {
  127. // 加权分配
  128. foreach ($userInfos as $userInfo) {
  129. $weight = $teamPvMap[$userInfo->getUserId()] / $totalTeamPv;
  130. $userReward = floor($levelTotal * $weight * 100) / 100;
  131. // 收集分配明细
  132. $distributionDetails[] = [
  133. 'user_id' => $userInfo->getUserId(),//用户ID
  134. 'level' => $key,//合伙人等级
  135. 'total_allocation' => $levelTotal,//该等级总分配金额
  136. 'team_pv' => $teamPvMap[$userInfo->getUserId()] ?? 0,//个人团队PV
  137. 'current_level_team_pv' => $totalTeamPv,//当前等级所有用户的团队PV总和
  138. 'ratio' => $weight,//占比(个人团队PV/当前等级团队PV)
  139. 'actual_amount' => $userReward,//实际发放金额
  140. ];
  141. $this->distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao, $isTest);
  142. }
  143. }
  144. }
  145. if (!$isTest) {
  146. // 记录分红日志
  147. // $this->dividendLog($start, $end, $conPri, $conPri, $config, $isTest, '共富体系合伙人分红');
  148. Db::commit();
  149. }
  150. // 返回分配明细数据
  151. return [
  152. 'success' => true,
  153. 'is_test' => $isTest,
  154. 'total_pv' => $totalConPri,
  155. 'allocatable_profit' => $conPri,
  156. 'distribution_details' => $distributionDetails,
  157. 'config' => $config,
  158. ];
  159. } catch (\Exception $e) {
  160. if (!$isTest) {
  161. Db::rollback();
  162. }
  163. throw new \Exception($e->getMessage());
  164. }
  165. }
  166. /**
  167. * 分发奖励(记录日志和更新余额)
  168. * @param $userInfo
  169. * @param $userReward
  170. * @param $radio
  171. * @param $levelTotal
  172. * @param $balanceDao
  173. * @param $userDao
  174. * @param bool $isTest 是否测试模式(不实际发放)
  175. * @return void
  176. */
  177. private function distributeReward($userInfo, $userReward, $radio, $levelTotal, $balanceDao, $userDao, $isTest = false)
  178. {
  179. $userId = $userInfo->getUserId();
  180. // 如果是测试模式,只记录日志不实际执行数据库操作
  181. if ($isTest) {
  182. Log::info("测试模式:用户 {$userId} 应发放奖励 {$userReward},等级 {$userInfo->getPartnerLevel()},比例 {$radio},等级总分红 {$levelTotal}");
  183. return;
  184. }
  185. $balanceDao->create([
  186. 'user_id' => $userId,
  187. 'level' => $userInfo->getPartnerLevel(),
  188. 'radio' => $radio,
  189. 'total_profit' => $levelTotal,
  190. 'old' => $userInfo->getBalance(),
  191. 'number' => $userReward,
  192. 'after' => $userInfo->getBalance() + $userReward
  193. ]);
  194. $bill = [
  195. 'uid' => $userId,
  196. 'link_id' => 0,
  197. 'pm' => 1,
  198. 'title' => '共富体系合伙人分佣(加权)',
  199. 'category' => 'now_money',
  200. 'type' => 'commission',
  201. 'number' => $userReward,
  202. 'balance' => 0,
  203. 'mark' => '共富体系合伙人分佣明细入账(加权)',
  204. 'create_time' => date('Y-m-d H:i:s'),
  205. 'status' => 1,
  206. 'commission_type' => CommonEnum::COMMISSION_TYPE['SHARED_PROSPERITY_PARTNER_COMMISSION']['code'],
  207. 'order_sn' => 0,
  208. 'tripartite' => 0,
  209. 'type_shop' => 0,
  210. 'mer_id' => 0,
  211. 'source' => 0,
  212. 'order_type' => 1,
  213. 'is_red_brokerage' => 0,
  214. 'gzc' => 3,
  215. 'take_time' => time(),
  216. 'district_id' => '',
  217. 'street_id' => '',
  218. 'month' => '',
  219. ];
  220. Db::name('user_bill')->insert($bill);
  221. $userDao->updateByUserId($userId, ['balance' => ['inc', $userReward]]);
  222. }
  223. // private function dividendLog($startTime, $endTime, $performance, $allocated, $starList, $isTest, $remark)
  224. // {
  225. // // 组装日志信息
  226. // /** @var DividendLogEntity $dividendLogEntity */
  227. // $dividendLogEntity = DividendLogEntity::newInstance();
  228. // $dividendLogEntity->setType(DividendLogEnum::TYPE['SharedProsperityPartnerProfit']['code'])
  229. // ->setStartTime($startTime)
  230. // ->setEndTime($endTime)
  231. // ->setPerformance($performance)
  232. // ->setAbatement(json_encode([]))
  233. // ->setAllocated($allocated)
  234. // ->setRule(json_encode($starList))
  235. // ->setIsTest($isTest)
  236. // ->setRemark($remark)
  237. // ->setExpected();
  238. // }
  239. /**
  240. * @param $data
  241. * @return true
  242. * @throws \think\db\exception\DataNotFoundException
  243. * @throws \think\db\exception\DbException
  244. * @throws \think\db\exception\ModelNotFoundException
  245. * @author 史晨
  246. * @date 2026/2/10 15:22
  247. * 感恩奖
  248. */
  249. public function thank($data)
  250. {
  251. // 获取时间范围内订单
  252. $start = $data['startTime'];
  253. $end = $data['endTime'];
  254. // 获取收入
  255. /** @var UserBillRepository $userBillRepository */
  256. $userBillRepository = app()->make(UserBillRepository::class);
  257. $billList = $userBillRepository->getListForSharedProsperityThank($start, $end);
  258. if (empty($billList)) {
  259. throw new \Exception('没有佣金记录,无需处理');
  260. }
  261. // /** @var SharedProsperityRewardRecordRepository $rewardRecordRepository */
  262. // $rewardRecordRepository = app()->make(SharedProsperityRewardRecordRepository::class);
  263. // $rewardList = $rewardRecordRepository->getListByTime($start, $end, self::REWARD_TYPE_PV);
  264. // if (empty($rewardList)) {
  265. // throw new \Exception('没有订单');
  266. // }
  267. // user_id分区求和 [id]=>[number]
  268. $groupedResult = array_reduce($billList, function ($carry, $item) {
  269. $userId = $item['uid'];
  270. $number = floatval($item['number']);
  271. if (!isset($carry[$userId])) {
  272. $carry[$userId] = 0;
  273. }
  274. $carry[$userId] += $number;
  275. return $carry;
  276. }, []);
  277. if (empty($groupedResult)) {
  278. throw new \Exception('分组求和错误');
  279. }
  280. /** @var SharedProsperityUserRepository $userRepository */
  281. $userRepository = app()->make(SharedProsperityUserRepository::class);
  282. /** @var SharedProsperityPointLogDao $balanceDao */
  283. $logDao = app()->make(SharedProsperityPointLogDao::class);
  284. DB::startTrans();
  285. try {
  286. foreach ($groupedResult as $key => $value) {
  287. // 获取感恩奖比例
  288. $radio = CommonEnum::ORDER_EARNING_ALLOCATION['SharedProsperity']['Thank']['code'];
  289. // 计算积分
  290. $points = floor($value * $radio * 100) / 100;
  291. // 获取上级
  292. $userData = $userRepository->getSharedProsperityUserEntityByUserId($key);
  293. if (empty($userData->getUserId())) {
  294. Log::info('共富感恩奖发放:' . $key . '不是共富会员,不发放感恩奖');
  295. continue;
  296. }
  297. $parentId = $userData->getParentId();
  298. if ($parentId == 0) {
  299. Log::info('共富感恩奖发放:' . $key . '的上级是0,不发放感恩奖');
  300. continue;
  301. }
  302. // 给上级发积分,记录日志
  303. if ($parentId > 0) {
  304. if ($points <= 0) {
  305. Log::info('共富感恩奖发放:' . $key . '的收入是' . $value . ',奖励占比是' . $radio . ',积分是' . $value * $radio . '所以' . $parentId . '发放感恩奖');
  306. continue;
  307. }
  308. $parentData = $userRepository->getSharedProsperityUserEntityByUserId($parentId);
  309. // 记录日志
  310. $logDao->create([
  311. 'user_id' => $parentId,
  312. 'total' => $value,
  313. 'radio' => $radio,
  314. 'old' => $parentData->getPoints(),
  315. 'number' => $points,
  316. 'after' => $parentData->getPoints() + $points
  317. ]);
  318. // 发放积分
  319. $userRepository->updateByUserId($parentId, ['points' => ['inc', $points]]);
  320. }
  321. }
  322. Db::commit();
  323. return true;
  324. } catch (\Exception $e) {
  325. DB::rollback();
  326. throw new \Exception($e->getMessage());
  327. }
  328. }
  329. /**
  330. * @param $data
  331. * @return array 分配明细数据
  332. * @throws \Exception
  333. * @author 史晨
  334. * @date 2026/2/14 10:54
  335. * 共富奖(团队平均分配)
  336. */
  337. public function group($data)
  338. {
  339. // 获取时间范围内订单
  340. $start = $data['startTime'];
  341. $end = $data['endTime'];
  342. $isTest = $data['isTest'] ?? false; // 是否测试模式(不实际发放)
  343. // 获取pv
  344. /** @var SharedProsperityRewardRecordRepository $rewardRecordRepository */
  345. $rewardRecordRepository = app()->make(SharedProsperityRewardRecordRepository::class);
  346. $orderList = $rewardRecordRepository->getListByTime($start, $end, self::REWARD_TYPE_PV);
  347. if (empty($orderList)) {
  348. throw new \Exception('没有订单');
  349. }
  350. // 计算总PV
  351. $totalPv = array_sum(array_column($orderList, 'number'));
  352. $totalPv = 100;
  353. // 可分配总额 = 总PV * 15%
  354. $totalAllocatable = floor($totalPv * 0.15 * 100) / 100;
  355. // 获取团队配置
  356. /** @var SharedProsperityGroupDao $groupDao */
  357. $groupDao = app()->make(SharedProsperityGroupDao::class);
  358. $groups = $groupDao->getAllGroups(); // 获取所有团队等级
  359. if (empty($groups)) {
  360. throw new \Exception('没有团队配置');
  361. }
  362. // 按 group_id 映射比例
  363. $groupRatios = [];
  364. foreach ($groups as $group) {
  365. $groupRatios[$group['id']] = [
  366. 'name' => $group['name'],
  367. 'radio' => $group['radio'], // 百分比,例如 8.00
  368. 'max' => $group['max']
  369. ];
  370. }
  371. // 获取团队成员
  372. /** @var SharedProsperityGroupUserDao $groupUserDao */
  373. $groupUserDao = app()->make(SharedProsperityGroupUserDao::class);
  374. $groupUsers = $groupUserDao->getValidUsersGroupByGroup(); // 获取有效用户按团队分组
  375. if (empty($groupUsers)) {
  376. throw new \Exception('没有团队成员');
  377. }
  378. /** @var SharedProsperityBalanceLogDao $balanceDao */
  379. $balanceDao = app()->make(SharedProsperityBalanceLogDao::class);
  380. /** @var SharedProsperityUserDao $userDao */
  381. $userDao = app()->make(SharedProsperityUserDao::class);
  382. // 分配明细数据收集
  383. $distributionDetails = [];
  384. // 如果是测试模式,不开启事务,只计算不实际发放
  385. if ($isTest === 'false') {
  386. Db::startTrans();
  387. }
  388. try {
  389. foreach ($groupRatios as $groupId => $groupInfo) {
  390. $ratio = $groupInfo['radio']; // 百分比
  391. // 该团队等级总分配金额 = 总PV * ratio / 100
  392. $groupTotal = floor($totalPv * $ratio / 100 * 100) / 100;
  393. // 获取该团队等级下的有效用户
  394. $userIds = $groupUsers[$groupId] ?? [];
  395. if (empty($userIds)) {
  396. // 没有成员,跳过分配
  397. continue;
  398. }
  399. $userCount = count($userIds);
  400. // 平均分配
  401. $eachReward = floor(($groupTotal / $userCount) * 100) / 100;
  402. // 获取用户信息
  403. $userInfos = $userDao->getSharedProsperityUserEntityListByUserIdList($userIds);
  404. if (empty($userInfos)) {
  405. continue;
  406. }
  407. foreach ($userInfos as $userInfo) {
  408. $userReward = $eachReward;
  409. // 收集分配明细
  410. $distributionDetails[] = [
  411. 'user_id' => $userInfo->getUserId(),
  412. 'group_id' => $groupId,
  413. 'group_name' => $groupInfo['name'],
  414. 'total_allocation' => $groupTotal,
  415. 'user_count' => $userCount,
  416. 'ratio' => $ratio,
  417. 'actual_amount' => $userReward,
  418. ];
  419. $this->distributeGroupReward($userInfo, $userReward, $ratio, $groupTotal, $balanceDao, $userDao, $groupId, $isTest);
  420. }
  421. }
  422. if ($isTest === 'false') {
  423. // 记录分红日志
  424. // $this->dividendLog($start, $end, $totalPv, $totalAllocatable, $groupRatios, $isTest, '共富体系团队奖');
  425. Db::commit();
  426. }
  427. // 返回分配明细数据
  428. return [
  429. 'success' => true,
  430. 'is_test' => $isTest,
  431. 'total_pv' => $totalPv,
  432. 'allocatable_profit' => $totalAllocatable,
  433. 'distribution_details' => $distributionDetails,
  434. 'group_ratios' => $groupRatios,
  435. ];
  436. } catch (\Exception $e) {
  437. if ($isTest === 'false') {
  438. Db::rollback();
  439. }
  440. throw new \Exception($e->getMessage());
  441. }
  442. }
  443. /**
  444. * 分发团队奖励(记录日志和更新余额)
  445. * @param $userInfo
  446. * @param $userReward
  447. * @param $radio
  448. * @param $groupTotal
  449. * @param $balanceDao
  450. * @param $userDao
  451. * @param $isTest
  452. * @return void
  453. */
  454. private function distributeGroupReward($userInfo, $userReward, $radio, $groupTotal, $balanceDao, $userDao, $groupId, $isTest)
  455. {
  456. $userId = $userInfo->getUserId();
  457. // 如果是测试模式,只记录日志不实际执行数据库操作
  458. if ($isTest === 'true') {
  459. Log::info("测试模式:用户 {$userId} 应发放团队奖励 {$userReward},比例 {$radio},团队总分红 {$groupTotal}");
  460. return;
  461. }
  462. $balanceDao->create([
  463. 'user_id' => $userId,
  464. 'level' => $groupId, // 共富团队等级
  465. 'radio' => $radio,
  466. 'total_profit' => $groupTotal,
  467. 'old' => $userInfo->getBalance(),
  468. 'number' => $userReward,
  469. 'after' => $userInfo->getBalance() + $userReward,
  470. 'type' => SharedProsperityBalanceLogEnum::TYPE['GROUP']['code']
  471. ]);
  472. $bill = [
  473. 'uid' => $userId,
  474. 'link_id' => 0,
  475. 'pm' => 1,
  476. 'title' => '共富体系团队奖',
  477. 'category' => 'now_money',
  478. 'type' => 'commission',
  479. 'number' => $userReward,
  480. 'balance' => 0,
  481. 'mark' => '共富体系团队奖明细入账',
  482. 'create_time' => date('Y-m-d H:i:s'),
  483. 'status' => 1,
  484. 'commission_type' => CommonEnum::COMMISSION_TYPE['SHARED_PROSPERITY_GROUP_COMMISSION']['code'],
  485. 'order_sn' => 0,
  486. 'tripartite' => 0,
  487. 'type_shop' => 0,
  488. 'mer_id' => 0,
  489. 'source' => 0,
  490. 'order_type' => 1,
  491. 'is_red_brokerage' => 0,
  492. 'gzc' => 3,
  493. 'take_time' => time(),
  494. 'district_id' => '',
  495. 'street_id' => '',
  496. 'month' => '',
  497. ];
  498. Db::name('user_bill')->insert($bill);
  499. $userDao->updateByUserId($userId, ['balance' => ['inc', $userReward]]);
  500. }
  501. }