ShareEarningReportService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <?php
  2. namespace App\Services;
  3. use App\Models\BalanceLog;
  4. use App\Models\ShareCommissionRecord;
  5. use App\Models\ShareSetting;
  6. use App\Models\User;
  7. use App\Models\UserLogin;
  8. use Illuminate\Support\Facades\DB;
  9. class ShareEarningReportService
  10. {
  11. public function memberDays(array $params): array
  12. {
  13. $range = !empty($params['start_date']) || !empty($params['end_date']) ? $this->dateRange($params) : null;
  14. $page = (int)($params['page'] ?? 1);
  15. $limit = (int)($params['limit'] ?? 20);
  16. $dateQuery = User::query()->whereNotNull('agent_user_code')->where('agent_user_code', '<>', '');
  17. if ($range !== null) {
  18. $dateQuery->whereBetween('created_at', [$range[0] . ' 00:00:00', $range[1] . ' 23:59:59']);
  19. }
  20. $dateQuery->selectRaw('DATE(created_at) AS stat_date')
  21. ->groupByRaw('DATE(created_at)')
  22. ->orderByDesc('stat_date');
  23. $dates = $dateQuery->pluck('stat_date');
  24. $pageDates = $dates->slice(($page - 1) * $limit, $limit)->values();
  25. if ($pageDates->isEmpty()) return ['total' => $dates->count(), 'data' => []];
  26. $pageStart = (string)$pageDates->min();
  27. $pageEnd = (string)$pageDates->max();
  28. $basicRows = User::query()->whereNotNull('agent_user_code')->where('agent_user_code', '<>', '')
  29. ->whereBetween('created_at', [$pageStart . ' 00:00:00', $pageEnd . ' 23:59:59'])
  30. ->selectRaw('DATE(created_at) AS stat_date')
  31. ->selectRaw('COUNT(*) AS registered_members')
  32. ->selectRaw('COUNT(DISTINCT agent_user_code) AS promoter_count')
  33. ->groupByRaw('DATE(created_at)')->get()->keyBy('stat_date');
  34. $inviteeBase = User::query()->whereNotNull('agent_user_code')->where('agent_user_code', '<>', '')
  35. ->whereBetween('created_at', [$pageStart . ' 00:00:00', $pageEnd . ' 23:59:59'])
  36. ->select(['id', 'member_id'])->selectRaw('DATE(created_at) AS stat_date');
  37. $memberMetrics = DB::query()->fromSub($inviteeBase, 'invitee')
  38. ->leftJoin('balance_logs as balance_log', 'balance_log.member_id', '=', 'invitee.member_id')
  39. ->select(['invitee.id', 'invitee.stat_date'])
  40. ->selectRaw('SUM(CASE WHEN change_type IN (?, ?) THEN amount ELSE 0 END) AS deposit', ['充值', '三方充值'])
  41. ->selectRaw('ABS(SUM(CASE WHEN change_type IN (?, ?, ?) THEN amount ELSE 0 END)) AS withdraw', ['提现', '三方提现', '人工提现'])
  42. ->selectRaw('ABS(SUM(CASE WHEN change_type LIKE ? THEN amount ELSE 0 END)) AS consume', ['%投注%'])
  43. ->selectRaw('SUM(CASE WHEN change_type IN (?, ?, ?, ?, ?, ?) THEN amount ELSE 0 END) AS bonus', ['注册赠送', '优惠活动', '返水', '回水', '笔笔返', '比比返'])
  44. ->groupBy(['invitee.id', 'invitee.stat_date']);
  45. $validMin = (float)(ShareSetting::query()->value('valid_member_min_deposit') ?? 0);
  46. $metricRows = DB::query()->fromSub($memberMetrics, 'member_metric')
  47. ->select('stat_date')
  48. ->selectRaw('SUM(deposit) AS deposit_total')
  49. ->selectRaw('SUM(withdraw) AS withdraw_total')
  50. ->selectRaw('SUM(consume) AS consume_total')
  51. ->selectRaw('SUM(bonus) AS bonus_total')
  52. ->selectRaw('SUM(CASE WHEN deposit >= ? THEN 1 ELSE 0 END) AS valid_members', [$validMin])
  53. ->groupBy('stat_date')->get()->keyBy('stat_date');
  54. $pageMemberIds = User::query()->whereNotNull('agent_user_code')->where('agent_user_code', '<>', '')
  55. ->whereBetween('created_at', [$pageStart . ' 00:00:00', $pageEnd . ' 23:59:59'])
  56. ->select('member_id');
  57. $firstLoginIds = UserLogin::query()->where('status', UserLogin::STATUS_SUCCESS)
  58. ->whereIn('user_id', $pageMemberIds)
  59. ->select('user_id')->selectRaw('MIN(id) AS first_login_id')->groupBy('user_id');
  60. $prefix = DB::getTablePrefix();
  61. $userAlias = $prefix . 'invitee_user';
  62. $loginAlias = $prefix . 'first_login_row';
  63. $platformRows = User::query()->from('users as invitee_user')
  64. ->leftJoinSub($firstLoginIds, 'first_login', 'first_login.user_id', '=', 'invitee_user.member_id')
  65. ->leftJoin('user_login as first_login_row', 'first_login_row.id', '=', 'first_login.first_login_id')
  66. ->whereNotNull('invitee_user.agent_user_code')->where('invitee_user.agent_user_code', '<>', '')
  67. ->whereBetween('invitee_user.created_at', [$pageStart . ' 00:00:00', $pageEnd . ' 23:59:59'])
  68. ->selectRaw("DATE({$userAlias}.created_at) AS stat_date")
  69. ->selectRaw("SUM(CASE WHEN {$loginAlias}.platform = ? THEN 1 ELSE 0 END) AS h5_registered", ['H5'])
  70. ->selectRaw("SUM(CASE WHEN {$loginAlias}.platform = ? THEN 1 ELSE 0 END) AS ios_registered", ['iOS'])
  71. ->selectRaw("SUM(CASE WHEN {$loginAlias}.platform = ? THEN 1 ELSE 0 END) AS android_registered", ['Android'])
  72. ->groupByRaw("DATE({$userAlias}.created_at)")->get()->keyBy('stat_date');
  73. $rows = $pageDates->map(function ($day) use ($basicRows, $metricRows, $platformRows) {
  74. $basic = $basicRows->get($day);
  75. $metric = $metricRows->get($day);
  76. $platform = $platformRows->get($day);
  77. $deposit = (float)($metric->deposit_total ?? 0);
  78. $withdraw = (float)($metric->withdraw_total ?? 0);
  79. $bonus = (float)($metric->bonus_total ?? 0);
  80. return [
  81. 'date' => (string)$day,
  82. 'promoter_count' => (int)($basic->promoter_count ?? 0),
  83. 'registered_members' => (int)($basic->registered_members ?? 0),
  84. 'valid_members' => (int)($metric->valid_members ?? 0),
  85. 'h5_registered' => (int)($platform->h5_registered ?? 0),
  86. 'ios_registered' => (int)($platform->ios_registered ?? 0),
  87. 'android_registered' => (int)($platform->android_registered ?? 0),
  88. 'deposit_total' => number_format($deposit, 4, '.', ''),
  89. 'consume_total' => number_format((float)($metric->consume_total ?? 0), 4, '.', ''),
  90. 'company_profit' => number_format($deposit - $withdraw - $bonus, 4, '.', ''),
  91. ];
  92. });
  93. return ['total' => $dates->count(), 'data' => $rows->values()];
  94. }
  95. public function promoterDetails(array $params): array
  96. {
  97. $day = $params['date'];
  98. $promoterCodes = User::query()->whereNotNull('agent_user_code')->where('agent_user_code', '<>', '')
  99. ->whereBetween('created_at', [$day . ' 00:00:00', $day . ' 23:59:59'])
  100. ->select('agent_user_code')->distinct();
  101. $query = User::query()->whereIn('user_code', $promoterCodes);
  102. if (!empty($params['member_id'])) $query->where('member_id', $params['member_id']);
  103. if (!empty($params['account'])) $query->where('account', 'like', '%' . $params['account'] . '%');
  104. $total = $query->count();
  105. $promoters = $query->orderByDesc('id')->forPage((int)($params['page'] ?? 1), (int)($params['limit'] ?? 20))->get();
  106. $invitees = User::query()->whereBetween('created_at', [$day . ' 00:00:00', $day . ' 23:59:59'])
  107. ->whereIn('agent_user_code', $promoters->pluck('user_code'))
  108. ->get(['id', 'member_id', 'agent_user_code']);
  109. $records = ShareCommissionRecord::query()->where('stat_date', $day)->where('status', 1)
  110. ->whereIn('promoter_user_id', $promoters->pluck('id'))->get()->groupBy('promoter_user_id');
  111. $setting = ShareSetting::query()->first();
  112. $metricsByMember = $this->moneyMetricsByMember($invitees->pluck('member_id')->all());
  113. $rows = $promoters->map(function ($promoter) use ($invitees, $records, $setting, $day, $metricsByMember) {
  114. $children = $invitees->where('agent_user_code', $promoter->user_code);
  115. $metrics = $this->sumMetrics($metricsByMember, $children->pluck('member_id')->all());
  116. $commission = $records->get($promoter->id, collect())->where('type', 'commission')->sum('amount');
  117. return [
  118. 'date' => $day,
  119. 'member_id' => (string)$promoter->member_id,
  120. 'account' => (string)$promoter->account,
  121. 'nickname' => (string)$promoter->first_name,
  122. 'deposit_total' => $metrics['deposit'],
  123. 'registered_count' => $children->count(),
  124. 'commission' => number_format((float)$commission, 4, '.', ''),
  125. 'current_fee_rate' => $setting ? (string)$setting->tier_one_rate : '0.0000',
  126. ];
  127. });
  128. return ['total' => $total, 'data' => $rows->values()];
  129. }
  130. public function registrationDetails(array $params): array
  131. {
  132. $query = User::query()->whereNotNull('agent_user_code')->where('agent_user_code', '<>', '')
  133. ->whereBetween('created_at', [$params['date'] . ' 00:00:00', $params['date'] . ' 23:59:59']);
  134. if (!empty($params['member_id'])) $query->where('member_id', $params['member_id']);
  135. if (!empty($params['account'])) $query->where('account', 'like', '%' . $params['account'] . '%');
  136. $total = $query->count();
  137. $users = $query->orderByDesc('id')->forPage((int)($params['page'] ?? 1), (int)($params['limit'] ?? 20))->get();
  138. $metricsByMember = $this->moneyMetricsByMember($users->pluck('member_id')->all());
  139. $rows = $users->map(function ($user) use ($params, $metricsByMember) {
  140. $metrics = $metricsByMember[(string)$user->member_id] ?? $this->emptyMetrics();
  141. return [
  142. 'date' => $params['date'],
  143. 'member_id' => (string)$user->member_id,
  144. 'account' => (string)$user->account,
  145. 'nickname' => (string)$user->first_name,
  146. 'deposit_total' => $metrics['deposit'],
  147. 'bet_total' => $metrics['consume'],
  148. 'withdraw_total' => $metrics['withdraw'],
  149. ];
  150. });
  151. return ['total' => $total, 'data' => $rows->values()];
  152. }
  153. public function summary(array $params): array
  154. {
  155. $range = !empty($params['start_date']) || !empty($params['end_date']) ? $this->dateRange($params) : null;
  156. $page = (int)($params['page'] ?? 1);
  157. $limit = (int)($params['limit'] ?? 20);
  158. $prefix = DB::getTablePrefix();
  159. $recordAlias = $prefix . 'share_record';
  160. $query = ShareCommissionRecord::query()->from('share_commission_records as share_record')
  161. ->leftJoin('users as promoter', 'promoter.id', '=', 'share_record.promoter_user_id')
  162. ->where('share_record.status', 1);
  163. if ($range !== null) $query->whereBetween('share_record.stat_date', $range);
  164. if (!empty($params['account'])) $query->where('promoter.account', 'like', '%' . $params['account'] . '%');
  165. $totalQuery = clone $query;
  166. $grouped = $query->select([
  167. 'share_record.stat_date', 'share_record.promoter_user_id',
  168. 'promoter.member_id as promoter_member_id', 'promoter.account as promoter_account',
  169. 'promoter.first_name as promoter_nickname',
  170. ])->selectRaw("SUM(CASE WHEN {$recordAlias}.type = 'commission' THEN {$recordAlias}.base_amount ELSE 0 END) AS deposit")
  171. ->selectRaw("SUM(CASE WHEN {$recordAlias}.type = 'commission' THEN {$recordAlias}.amount ELSE 0 END) AS commission")
  172. ->selectRaw("SUM(CASE WHEN {$recordAlias}.type = 'fee_waiver' THEN {$recordAlias}.base_amount ELSE 0 END) AS sales_amount")
  173. ->selectRaw("MAX(CASE WHEN {$recordAlias}.type = 'fee_waiver' THEN {$recordAlias}.rate ELSE 0 END) AS fee_waiver_rate")
  174. ->groupBy([
  175. 'share_record.stat_date', 'share_record.promoter_user_id',
  176. 'promoter.member_id', 'promoter.account', 'promoter.first_name',
  177. ]);
  178. $total = DB::query()->fromSub(clone $grouped, 'share_summary')->count();
  179. $rows = $grouped->orderByDesc('share_record.stat_date')->orderByDesc('share_record.promoter_user_id')
  180. ->forPage($page, $limit)->get()->map(fn ($row) => [
  181. 'date' => $row->stat_date instanceof \DateTimeInterface
  182. ? $row->stat_date->format('Y-m-d')
  183. : substr((string)$row->stat_date, 0, 10),
  184. 'promoter_member_id' => (string)($row->promoter_member_id ?? ''),
  185. 'promoter_account' => (string)($row->promoter_account ?? ''),
  186. 'promoter_nickname' => (string)($row->promoter_nickname ?? ''),
  187. 'deposit' => number_format((float)$row->deposit, 4, '.', ''),
  188. 'commission' => number_format((float)$row->commission, 4, '.', ''),
  189. 'sales_amount' => number_format((float)$row->sales_amount, 4, '.', ''),
  190. 'fee_waiver_rate' => number_format((float)$row->fee_waiver_rate, 4, '.', ''),
  191. ]);
  192. $totals = $totalQuery->selectRaw("SUM(CASE WHEN {$recordAlias}.type = 'commission' THEN {$recordAlias}.amount ELSE 0 END) AS total_commission")
  193. ->selectRaw("MAX(CASE WHEN {$recordAlias}.type = 'fee_waiver' THEN {$recordAlias}.rate ELSE 0 END) AS highest_fee_waiver_rate")->first();
  194. return [
  195. 'total' => $total,
  196. 'data' => $rows->values(),
  197. 'summary' => [
  198. 'total_commission' => number_format((float)($totals->total_commission ?? 0), 4, '.', ''),
  199. 'highest_fee_waiver_rate' => number_format((float)($totals->highest_fee_waiver_rate ?? 0), 4, '.', ''),
  200. ],
  201. ];
  202. }
  203. private function moneyMetricsByMember(array $memberIds): array
  204. {
  205. $memberIds = array_values(array_unique(array_filter(array_map('strval', $memberIds))));
  206. if (!$memberIds) return [];
  207. $metrics = array_fill_keys($memberIds, $this->emptyMetrics());
  208. $sets = [
  209. 'deposit' => fn ($query) => $query->whereIn('change_type', ['充值', '三方充值']),
  210. 'withdraw' => fn ($query) => $query->whereIn('change_type', ['提现', '三方提现', '人工提现']),
  211. 'consume' => fn ($query) => $query->where('change_type', 'like', '%投注%'),
  212. 'bonus' => fn ($query) => $query->whereIn('change_type', ['注册赠送', '优惠活动', '返水', '回水', '笔笔返', '比比返']),
  213. ];
  214. foreach ($sets as $field => $scope) {
  215. $query = BalanceLog::query()->whereIn('member_id', $memberIds);
  216. $rows = $scope($query)->groupBy('member_id')->get(['member_id', DB::raw('SUM(amount) as total')]);
  217. foreach ($rows as $row) {
  218. $value = (float)$row->total;
  219. $metrics[(string)$row->member_id][$field] = number_format(in_array($field, ['withdraw', 'consume'], true) ? abs($value) : $value, 4, '.', '');
  220. }
  221. }
  222. return $metrics;
  223. }
  224. private function sumMetrics(array $metricsByMember, array $memberIds): array
  225. {
  226. $totals = ['deposit' => 0.0, 'withdraw' => 0.0, 'consume' => 0.0, 'bonus' => 0.0];
  227. foreach (array_unique(array_map('strval', $memberIds)) as $memberId) {
  228. foreach ($totals as $field => $value) {
  229. $totals[$field] += (float)($metricsByMember[$memberId][$field] ?? 0);
  230. }
  231. }
  232. return array_map(fn ($value) => number_format($value, 4, '.', ''), $totals);
  233. }
  234. private function emptyMetrics(): array
  235. {
  236. return ['deposit' => '0.0000', 'withdraw' => '0.0000', 'consume' => '0.0000', 'bonus' => '0.0000'];
  237. }
  238. private function dateRange(array $params): array
  239. {
  240. $end = $params['end_date'] ?? date('Y-m-d');
  241. $start = $params['start_date'] ?? date('Y-m-d', strtotime($end . ' -30 days'));
  242. if (strtotime($start) > strtotime($end)) {
  243. throw new \RuntimeException('结束日期不能早于开始日期');
  244. }
  245. if (strtotime($end) - strtotime($start) > 366 * 86400) {
  246. throw new \RuntimeException('单次查询日期范围不能超过 366 天');
  247. }
  248. return [$start, $end];
  249. }
  250. }