ShareEarningReportService.php 16 KB

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