UrsProfitLogic.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. <?php
  2. namespace App\Module\UrsPromotion\Logics;
  3. use App\Module\UrsPromotion\Models\UrsUserReferral;
  4. use App\Module\UrsPromotion\Models\UrsUserTalent;
  5. use App\Module\UrsPromotion\Models\UrsProfit;
  6. use App\Module\UrsPromotion\Models\UrsTalentConfig;
  7. use App\Module\UrsPromotion\Enums\UrsProfitType;
  8. use App\Module\UrsPromotion\Enums\UrsPromotionRelationLevel;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Log;
  11. /**
  12. * URS收益分成逻辑类
  13. *
  14. * 处理URS推广系统的收益分成逻辑,支持三代推广关系
  15. */
  16. class UrsProfitLogic
  17. {
  18. /**
  19. * 计算并分发URS推广收益(按人头奖励)
  20. *
  21. * @param int $userId 新注册用户ID
  22. * @param string $sourceType 收益来源类型
  23. * @param int $sourceId 收益来源ID
  24. * @return array 分成记录
  25. */
  26. public function distributePromotionReward(
  27. int $userId,
  28. string $sourceType,
  29. int $sourceId
  30. ): array {
  31. $profits = [];
  32. try {
  33. // 获取用户的推荐关系链(三代)
  34. $referralChain = $this->getUserReferralChain($userId);
  35. if (empty($referralChain)) {
  36. Log::info("用户 {$userId} 无推荐关系,无需分成");
  37. return $profits;
  38. }
  39. // 获取达人等级配置
  40. $talentConfigs = $this->getTalentConfigs();
  41. // 为每一级推荐人发放奖励
  42. foreach ($referralChain as $level => $referrerId) {
  43. $profit = $this->calculatePromotionReward(
  44. $referrerId,
  45. $userId,
  46. $sourceType,
  47. $sourceId,
  48. $level,
  49. $talentConfigs
  50. );
  51. if ($profit) {
  52. $profits[] = $profit;
  53. }
  54. }
  55. Log::info("用户 {$userId} 推广收益分发完成", [
  56. 'source_type' => $sourceType,
  57. 'source_id' => $sourceId,
  58. 'profits_count' => count($profits)
  59. ]);
  60. } catch (\Exception $e) {
  61. Log::error("URS推广收益分发失败", [
  62. 'user_id' => $userId,
  63. 'source_type' => $sourceType,
  64. 'source_id' => $sourceId,
  65. 'error' => $e->getMessage()
  66. ]);
  67. }
  68. return $profits;
  69. }
  70. /**
  71. * 计算并分发URS种植收益(按比例分成)
  72. *
  73. * @param int $userId 产生收益的用户ID
  74. * @param string $sourceType 收益来源类型
  75. * @param int $sourceId 收益来源ID
  76. * @param string $originalAmount 原始收益金额
  77. * @return array 分成记录
  78. */
  79. public function distributePlantingReward(
  80. int $userId,
  81. string $sourceType,
  82. int $sourceId,
  83. string $originalAmount
  84. ): array {
  85. $profits = [];
  86. try {
  87. // 获取用户的推荐关系链(三代)
  88. $referralChain = $this->getUserReferralChain($userId);
  89. if (empty($referralChain)) {
  90. Log::info("用户 {$userId} 无推荐关系,无需分成");
  91. return $profits;
  92. }
  93. // 获取达人等级配置
  94. $talentConfigs = $this->getTalentConfigs();
  95. // 为每一级推荐人计算分成
  96. foreach ($referralChain as $level => $referrerId) {
  97. $profit = $this->calculatePlantingReward(
  98. $referrerId,
  99. $userId,
  100. $sourceType,
  101. $sourceId,
  102. $level,
  103. $originalAmount,
  104. $talentConfigs
  105. );
  106. if ($profit) {
  107. $profits[] = $profit;
  108. }
  109. }
  110. Log::info("用户 {$userId} 种植收益分成完成", [
  111. 'source_type' => $sourceType,
  112. 'source_id' => $sourceId,
  113. 'original_amount' => $originalAmount,
  114. 'profits_count' => count($profits)
  115. ]);
  116. } catch (\Exception $e) {
  117. Log::error("URS种植收益分成失败", [
  118. 'user_id' => $userId,
  119. 'source_type' => $sourceType,
  120. 'source_id' => $sourceId,
  121. 'error' => $e->getMessage()
  122. ]);
  123. }
  124. return $profits;
  125. }
  126. /**
  127. * 获取用户的推荐关系链(三代)
  128. *
  129. * @param int $userId 用户ID
  130. * @return array [level => referrer_id] 1:直推 2:间推 3:三推
  131. */
  132. private function getUserReferralChain(int $userId): array
  133. {
  134. $chain = [];
  135. $currentUserId = $userId;
  136. // 最多查找三代
  137. for ($level = 1; $level <= UrsPromotionRelationLevel::getMaxLevel(); $level++) {
  138. $referral = UrsUserReferral::where('user_id', $currentUserId)
  139. ->where('status', UrsUserReferral::STATUS_VALID)
  140. ->first();
  141. if (!$referral) {
  142. break;
  143. }
  144. $chain[$level] = $referral->referrer_id;
  145. $currentUserId = $referral->referrer_id;
  146. }
  147. return $chain;
  148. }
  149. /**
  150. * 获取达人等级配置
  151. *
  152. * @return array [level => config]
  153. */
  154. private function getTalentConfigs(): array
  155. {
  156. static $configs = null;
  157. if ($configs === null) {
  158. $configs = UrsTalentConfig::where('status', UrsTalentConfig::STATUS_ENABLED)
  159. ->get()
  160. ->keyBy('level')
  161. ->toArray();
  162. }
  163. return $configs;
  164. }
  165. /**
  166. * 计算推广收益奖励(按人头)
  167. *
  168. * @param int $referrerId 推荐人ID
  169. * @param int $memberId 新注册用户ID
  170. * @param string $sourceType 收益来源类型
  171. * @param int $sourceId 收益来源ID
  172. * @param int $relationLevel 推荐层级
  173. * @param array $talentConfigs 达人等级配置
  174. * @return UrsProfit|null
  175. */
  176. private function calculatePromotionReward(
  177. int $referrerId,
  178. int $memberId,
  179. string $sourceType,
  180. int $sourceId,
  181. int $relationLevel,
  182. array $talentConfigs
  183. ): ?UrsProfit {
  184. // 获取推荐人的达人等级
  185. $talent = UrsUserTalent::where('user_id', $referrerId)->first();
  186. $talentLevel = $talent ? $talent->talent_level : 0;
  187. // 获取对应等级的配置
  188. $config = $talentConfigs[$talentLevel] ?? null;
  189. if (!$config) {
  190. Log::warning("推荐人 {$referrerId} 达人等级 {$talentLevel} 配置不存在");
  191. return null;
  192. }
  193. // 获取奖励组ID
  194. $rewardGroupId = $this->getPromotionRewardGroupId($config, $relationLevel);
  195. if (!$rewardGroupId) {
  196. Log::debug("推荐人 {$referrerId} 等级 {$talentLevel} 层级 {$relationLevel} 无奖励组配置");
  197. return null;
  198. }
  199. // TODO: 这里需要根据奖励组ID获取具体的奖励金额
  200. // 暂时使用固定金额作为示例
  201. $rewardAmount = $this->getRewardAmountByGroupId($rewardGroupId);
  202. // 创建收益记录
  203. $profit = UrsProfit::create([
  204. 'user_id' => $referrerId,
  205. 'promotion_member_id' => $memberId,
  206. 'source_id' => $sourceId,
  207. 'source_type' => $sourceType,
  208. 'profit_type' => UrsProfitType::PROMOTION_REWARD->value,
  209. 'relation_level' => $relationLevel,
  210. 'original_amount' => '0', // 推广收益无原始金额概念
  211. 'profit_amount' => $rewardAmount,
  212. 'profit_rate' => 0, // 推广收益无比例概念
  213. 'reward_group_id' => $rewardGroupId,
  214. 'talent_level' => $talentLevel,
  215. 'status' => UrsProfit::STATUS_NORMAL,
  216. ]);
  217. Log::info("URS推广收益记录创建", [
  218. 'profit_id' => $profit->id,
  219. 'referrer_id' => $referrerId,
  220. 'member_id' => $memberId,
  221. 'relation_level' => $relationLevel,
  222. 'talent_level' => $talentLevel,
  223. 'reward_group_id' => $rewardGroupId,
  224. 'reward_amount' => $rewardAmount
  225. ]);
  226. return $profit;
  227. }
  228. /**
  229. * 计算种植收益分成(按比例)
  230. *
  231. * @param int $referrerId 推荐人ID
  232. * @param int $memberId 团队成员ID
  233. * @param string $sourceType 收益来源类型
  234. * @param int $sourceId 收益来源ID
  235. * @param int $relationLevel 推荐层级
  236. * @param string $originalAmount 原始收益金额
  237. * @param array $talentConfigs 达人等级配置
  238. * @return UrsProfit|null
  239. */
  240. private function calculatePlantingReward(
  241. int $referrerId,
  242. int $memberId,
  243. string $sourceType,
  244. int $sourceId,
  245. int $relationLevel,
  246. string $originalAmount,
  247. array $talentConfigs
  248. ): ?UrsProfit {
  249. // 获取推荐人的达人等级
  250. $talent = UrsUserTalent::where('user_id', $referrerId)->first();
  251. $talentLevel = $talent ? $talent->talent_level : 0;
  252. // 获取对应等级的配置
  253. $config = $talentConfigs[$talentLevel] ?? null;
  254. if (!$config) {
  255. Log::warning("推荐人 {$referrerId} 达人等级 {$talentLevel} 配置不存在");
  256. return null;
  257. }
  258. // 获取分成比例
  259. $profitRate = $this->getPlantingRewardRate($config, $relationLevel);
  260. if ($profitRate <= 0) {
  261. Log::debug("推荐人 {$referrerId} 等级 {$talentLevel} 层级 {$relationLevel} 分成比例为0");
  262. return null;
  263. }
  264. // 计算分成金额
  265. $profitAmount = bcmul($originalAmount, (string)$profitRate, 10);
  266. // 创建收益记录
  267. $profit = UrsProfit::create([
  268. 'user_id' => $referrerId,
  269. 'promotion_member_id' => $memberId,
  270. 'source_id' => $sourceId,
  271. 'source_type' => $sourceType,
  272. 'profit_type' => UrsProfitType::PLANTING_REWARD->value,
  273. 'relation_level' => $relationLevel,
  274. 'original_amount' => $originalAmount,
  275. 'profit_amount' => $profitAmount,
  276. 'profit_rate' => $profitRate,
  277. 'reward_group_id' => null, // 种植收益不使用奖励组
  278. 'talent_level' => $talentLevel,
  279. 'status' => UrsProfit::STATUS_NORMAL,
  280. ]);
  281. Log::info("URS种植收益记录创建", [
  282. 'profit_id' => $profit->id,
  283. 'referrer_id' => $referrerId,
  284. 'member_id' => $memberId,
  285. 'relation_level' => $relationLevel,
  286. 'talent_level' => $talentLevel,
  287. 'profit_rate' => $profitRate,
  288. 'profit_amount' => $profitAmount
  289. ]);
  290. return $profit;
  291. }
  292. /**
  293. * 获取推广收益奖励组ID
  294. *
  295. * @param array $config 达人等级配置
  296. * @param int $relationLevel 推荐层级
  297. * @return int|null
  298. */
  299. private function getPromotionRewardGroupId(array $config, int $relationLevel): ?int
  300. {
  301. // 使用新的独立字段结构
  302. switch ($relationLevel) {
  303. case 1: // 直推
  304. return $config['promotion_direct_group'] ?? null;
  305. case 2: // 间推
  306. return $config['promotion_indirect_group'] ?? null;
  307. case 3: // 三推
  308. return $config['promotion_third_group'] ?? null;
  309. default:
  310. return null;
  311. }
  312. }
  313. /**
  314. * 获取种植收益分成比例
  315. *
  316. * @param array $config 达人等级配置
  317. * @param int $relationLevel 推荐层级
  318. * @return float
  319. */
  320. private function getPlantingRewardRate(array $config, int $relationLevel): float
  321. {
  322. // 使用新的独立字段结构
  323. switch ($relationLevel) {
  324. case 1: // 直推
  325. return (float)($config['planting_direct_rate'] ?? 0);
  326. case 2: // 间推
  327. return (float)($config['planting_indirect_rate'] ?? 0);
  328. case 3: // 三推
  329. return (float)($config['planting_third_rate'] ?? 0);
  330. default:
  331. return 0;
  332. }
  333. }
  334. /**
  335. * 根据奖励组ID获取奖励金额
  336. *
  337. * TODO: 这里需要集成奖励组系统,暂时使用固定金额
  338. *
  339. * @param int $rewardGroupId 奖励组ID
  340. * @return string
  341. */
  342. private function getRewardAmountByGroupId(int $rewardGroupId): string
  343. {
  344. // 暂时使用固定金额映射,实际应该从奖励组系统获取
  345. $rewardAmounts = [
  346. 1001 => '50.0000000000', // 初级达人直推奖励
  347. 1002 => '30.0000000000', // 初级达人间推奖励
  348. 1003 => '10.0000000000', // 初级达人三推奖励
  349. 1004 => '80.0000000000', // 中级达人直推奖励
  350. 1005 => '50.0000000000', // 中级达人间推奖励
  351. 1006 => '20.0000000000', // 中级达人三推奖励
  352. 1007 => '120.0000000000', // 高级达人直推奖励
  353. 1008 => '80.0000000000', // 高级达人间推奖励
  354. 1009 => '40.0000000000', // 高级达人三推奖励
  355. 1010 => '200.0000000000', // 资深达人直推奖励
  356. 1011 => '120.0000000000', // 资深达人间推奖励
  357. 1012 => '60.0000000000', // 资深达人三推奖励
  358. 1013 => '300.0000000000', // 顶级达人直推奖励
  359. 1014 => '200.0000000000', // 顶级达人间推奖励
  360. 1015 => '100.0000000000', // 顶级达人三推奖励
  361. ];
  362. return $rewardAmounts[$rewardGroupId] ?? '0.0000000000';
  363. }
  364. /**
  365. * 获取用户的收益统计
  366. *
  367. * @param int $userId 用户ID
  368. * @param UrsProfitType|null $profitType 收益类型
  369. * @param string|null $startDate 开始日期
  370. * @param string|null $endDate 结束日期
  371. * @return array
  372. */
  373. public function getUserProfitStats(
  374. int $userId,
  375. ?UrsProfitType $profitType = null,
  376. ?string $startDate = null,
  377. ?string $endDate = null
  378. ): array {
  379. $query = UrsProfit::where('user_id', $userId)
  380. ->where('status', UrsProfit::STATUS_NORMAL);
  381. if ($profitType) {
  382. $query->where('profit_type', $profitType->value);
  383. }
  384. if ($startDate) {
  385. $query->where('created_at', '>=', $startDate);
  386. }
  387. if ($endDate) {
  388. $query->where('created_at', '<=', $endDate);
  389. }
  390. $profits = $query->get();
  391. $stats = [
  392. 'total_amount' => '0',
  393. 'total_count' => 0,
  394. 'by_type' => [],
  395. 'by_level' => [],
  396. ];
  397. foreach ($profits as $profit) {
  398. $stats['total_amount'] = bcadd($stats['total_amount'], $profit->profit_amount, 10);
  399. $stats['total_count']++;
  400. // 按收益类型统计
  401. $type = $profit->profit_type;
  402. if (!isset($stats['by_type'][$type])) {
  403. $stats['by_type'][$type] = ['amount' => '0', 'count' => 0];
  404. }
  405. $stats['by_type'][$type]['amount'] = bcadd($stats['by_type'][$type]['amount'], $profit->profit_amount, 10);
  406. $stats['by_type'][$type]['count']++;
  407. // 按推荐层级统计
  408. $level = $profit->relation_level;
  409. if (!isset($stats['by_level'][$level])) {
  410. $stats['by_level'][$level] = ['amount' => '0', 'count' => 0];
  411. }
  412. $stats['by_level'][$level]['amount'] = bcadd($stats['by_level'][$level]['amount'], $profit->profit_amount, 10);
  413. $stats['by_level'][$level]['count']++;
  414. }
  415. return $stats;
  416. }
  417. }