FundsSummaryService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <?php
  2. namespace App\Services;
  3. use Carbon\Carbon;
  4. use Illuminate\Support\Facades\DB;
  5. class FundsSummaryService
  6. {
  7. public static function summary(string $startDate, string $endDate): array
  8. {
  9. [$start, $end] = self::dateRange($startDate, $endDate);
  10. $balance = self::balanceAggregates($start, $end);
  11. $withdraw = self::withdrawAggregates($start, $end);
  12. $paymentOrder = self::paymentOrderAggregates($start, $end);
  13. $combinedPeople = self::combinedPeople($start, $end);
  14. $income = [
  15. self::item('company_deposit', '公司入款', $balance->company_deposit_amount, $balance->company_deposit_people),
  16. self::item(
  17. 'third_party_deposit',
  18. '第三方支付(入款)',
  19. $balance->third_party_deposit_amount,
  20. $balance->third_party_deposit_people
  21. ),
  22. self::item('manual_credit', '人工上分', $balance->manual_credit_amount, $balance->manual_credit_people),
  23. self::item(
  24. 'member_withdraw_deduction',
  25. '会员出款扣除',
  26. (float)$withdraw->member_withdraw_deduction_amount
  27. + (float)$paymentOrder->manual_withdrawal_fee_amount,
  28. $combinedPeople['member_withdraw_deduction']
  29. ),
  30. ];
  31. $expense = [
  32. self::item(
  33. 'member_withdrawal',
  34. '会员出款',
  35. $withdraw->member_withdrawal_amount,
  36. $withdraw->member_withdrawal_people
  37. ),
  38. self::item(
  39. 'manual_withdrawal',
  40. '人工出款',
  41. (float)$balance->manual_debit_amount + (float)$paymentOrder->manual_withdrawal_amount,
  42. $combinedPeople['manual_withdrawal']
  43. ),
  44. self::item('vip_discount', '给予优惠(VIP 折扣)', 0, 0),
  45. self::item('rebate', '给予返点', $balance->rebate_amount, $balance->rebate_people),
  46. self::item('bonus', '优惠金', $balance->bonus_amount, $balance->bonus_people),
  47. self::item(
  48. 'third_party_fee',
  49. '第三方手续费',
  50. $paymentOrder->third_party_fee_amount,
  51. $paymentOrder->third_party_fee_people
  52. ),
  53. self::item(
  54. 'third_party_auto_withdrawal',
  55. '第三方支付出款(自动出款)',
  56. $paymentOrder->auto_withdrawal_amount,
  57. $paymentOrder->auto_withdrawal_people
  58. ),
  59. self::item(
  60. 'third_party_auto_withdrawal_fee',
  61. '第三方支付自动出款手续费',
  62. $paymentOrder->auto_withdrawal_fee_amount,
  63. $paymentOrder->auto_withdrawal_fee_people
  64. ),
  65. ];
  66. return [
  67. 'start_date' => $startDate,
  68. 'end_date' => $endDate,
  69. 'income' => $income,
  70. 'expense' => $expense,
  71. 'income_total' => self::sumItems($income),
  72. 'expense_total' => self::sumItems($expense),
  73. ];
  74. }
  75. private static function balanceAggregates(Carbon $start, Carbon $end): object
  76. {
  77. return DB::table('balance_logs')
  78. ->whereBetween('created_at', [$start, $end])
  79. ->whereIn('change_type', [
  80. '充值', '三方充值', '人工充值', '人工扣款',
  81. '比比返', '笔笔返', '返水', '回水',
  82. '注册赠送', '优惠活动', '即充即送', '充值返现', '老用户回归',
  83. ])
  84. ->selectRaw(<<<'SQL'
  85. COALESCE(SUM(CASE
  86. WHEN change_type = '充值' AND amount > 0 THEN amount ELSE 0 END), 0
  87. ) AS company_deposit_amount,
  88. COUNT(DISTINCT CASE
  89. WHEN change_type = '充值' AND amount > 0 THEN member_id END
  90. ) AS company_deposit_people,
  91. COALESCE(SUM(CASE
  92. WHEN change_type = '三方充值' AND amount > 0 THEN amount ELSE 0 END), 0
  93. ) AS third_party_deposit_amount,
  94. COUNT(DISTINCT CASE
  95. WHEN change_type = '三方充值' AND amount > 0 THEN member_id END
  96. ) AS third_party_deposit_people,
  97. COALESCE(SUM(CASE
  98. WHEN change_type = '人工充值' AND related_id IS NULL AND amount > 0 THEN amount ELSE 0 END), 0
  99. ) AS manual_credit_amount,
  100. COUNT(DISTINCT CASE
  101. WHEN change_type = '人工充值' AND related_id IS NULL AND amount > 0 THEN member_id END
  102. ) AS manual_credit_people,
  103. COALESCE(SUM(CASE
  104. WHEN change_type = '人工扣款' AND amount < 0 THEN ABS(amount) ELSE 0 END), 0
  105. ) AS manual_debit_amount,
  106. COALESCE(SUM(CASE
  107. WHEN change_type IN ('比比返', '笔笔返', '返水', '回水') AND amount > 0 THEN amount ELSE 0 END), 0
  108. ) AS rebate_amount,
  109. COUNT(DISTINCT CASE
  110. WHEN change_type IN ('比比返', '笔笔返', '返水', '回水') AND amount > 0 THEN member_id END
  111. ) AS rebate_people,
  112. COALESCE(SUM(CASE
  113. WHEN (
  114. change_type IN ('注册赠送', '优惠活动', '即充即送', '充值返现', '老用户回归')
  115. OR (change_type = '人工充值' AND related_id = 0)
  116. ) AND amount > 0 THEN amount ELSE 0 END), 0
  117. ) AS bonus_amount,
  118. COUNT(DISTINCT CASE
  119. WHEN (
  120. change_type IN ('注册赠送', '优惠活动', '即充即送', '充值返现', '老用户回归')
  121. OR (change_type = '人工充值' AND related_id = 0)
  122. ) AND amount > 0 THEN member_id END
  123. ) AS bonus_people
  124. SQL)
  125. ->first();
  126. }
  127. private static function withdrawAggregates(Carbon $start, Carbon $end): object
  128. {
  129. $balanceLogAlias = self::rawAlias('bl');
  130. $withdrawAlias = self::rawAlias('w');
  131. return self::successfulWithdrawLogs($start, $end)
  132. ->selectRaw(<<<SQL
  133. COALESCE(SUM(ABS({$balanceLogAlias}.`amount`)), 0) AS member_withdrawal_amount,
  134. COUNT(DISTINCT {$balanceLogAlias}.`member_id`) AS member_withdrawal_people,
  135. COALESCE(SUM(CASE
  136. WHEN {$withdrawAlias}.`service_charge` > 0
  137. THEN ABS({$balanceLogAlias}.`amount`) * {$withdrawAlias}.`service_charge`
  138. / NULLIF({$withdrawAlias}.`amount`, 0)
  139. ELSE 0 END), 0
  140. ) AS member_withdraw_deduction_amount
  141. SQL)
  142. ->first();
  143. }
  144. private static function paymentOrderAggregates(Carbon $start, Carbon $end): object
  145. {
  146. $pay = PaymentOrderService::TYPE_PAY;
  147. $payout = PaymentOrderService::TYPE_PAYOUT;
  148. $manualPayout = PaymentOrderService::TYPE_SELF_PAYOUT;
  149. return DB::table('payment_orders')
  150. ->where('status', PaymentOrderService::STATUS_SUCCESS)
  151. ->whereIn('type', [$pay, $payout, $manualPayout])
  152. ->whereBetween('created_at', [$start, $end])
  153. ->selectRaw(<<<SQL
  154. COALESCE(SUM(CASE WHEN type = {$pay} AND fee > 0 THEN fee ELSE 0 END), 0)
  155. AS third_party_fee_amount,
  156. COUNT(DISTINCT CASE WHEN type = {$pay} AND fee > 0 THEN member_id END)
  157. AS third_party_fee_people,
  158. COALESCE(SUM(CASE WHEN type = {$payout} THEN amount ELSE 0 END), 0)
  159. AS auto_withdrawal_amount,
  160. COUNT(DISTINCT CASE WHEN type = {$payout} THEN member_id END)
  161. AS auto_withdrawal_people,
  162. COALESCE(SUM(CASE WHEN type = {$payout} AND fee > 0 THEN fee ELSE 0 END), 0)
  163. AS auto_withdrawal_fee_amount,
  164. COUNT(DISTINCT CASE WHEN type = {$payout} AND fee > 0 THEN member_id END)
  165. AS auto_withdrawal_fee_people,
  166. COALESCE(SUM(CASE WHEN type = {$manualPayout} THEN amount ELSE 0 END), 0)
  167. AS manual_withdrawal_amount,
  168. COALESCE(SUM(CASE WHEN type = {$manualPayout} AND fee > 0 THEN fee ELSE 0 END), 0)
  169. AS manual_withdrawal_fee_amount
  170. SQL)
  171. ->first();
  172. }
  173. /**
  174. * Count unique members across categories backed by more than one table.
  175. *
  176. * @return array{member_withdraw_deduction:int, manual_withdrawal:int}
  177. */
  178. private static function combinedPeople(Carbon $start, Carbon $end): array
  179. {
  180. $withdrawFees = self::successfulWithdrawLogs($start, $end)
  181. ->where('w.service_charge', '>', 0)
  182. ->selectRaw("'member_withdraw_deduction' AS category")
  183. ->addSelect('bl.member_id as member_id');
  184. $manualPayoutFees = DB::table('payment_orders as po')
  185. ->where('po.type', PaymentOrderService::TYPE_SELF_PAYOUT)
  186. ->where('po.status', PaymentOrderService::STATUS_SUCCESS)
  187. ->where('po.fee', '>', 0)
  188. ->whereBetween('po.created_at', [$start, $end])
  189. ->selectRaw("'member_withdraw_deduction' AS category")
  190. ->addSelect('po.member_id as member_id');
  191. $manualDebits = DB::table('balance_logs as bl')
  192. ->where('bl.change_type', '人工扣款')
  193. ->where('bl.amount', '<', 0)
  194. ->whereBetween('bl.created_at', [$start, $end])
  195. ->selectRaw("'manual_withdrawal' AS category")
  196. ->addSelect('bl.member_id as member_id');
  197. $manualPayouts = DB::table('payment_orders as po')
  198. ->where('po.type', PaymentOrderService::TYPE_SELF_PAYOUT)
  199. ->where('po.status', PaymentOrderService::STATUS_SUCCESS)
  200. ->whereBetween('po.created_at', [$start, $end])
  201. ->selectRaw("'manual_withdrawal' AS category")
  202. ->addSelect('po.member_id as member_id');
  203. $union = $withdrawFees
  204. ->unionAll($manualPayoutFees)
  205. ->unionAll($manualDebits)
  206. ->unionAll($manualPayouts);
  207. $rows = DB::query()
  208. ->fromSub($union, 'finance_members')
  209. ->whereNotNull('member_id')
  210. ->select('category')
  211. ->selectRaw('COUNT(DISTINCT member_id) AS people')
  212. ->groupBy('category')
  213. ->get();
  214. $result = [
  215. 'member_withdraw_deduction' => 0,
  216. 'manual_withdrawal' => 0,
  217. ];
  218. foreach ($rows as $row) {
  219. $result[(string)$row->category] = (int)$row->people;
  220. }
  221. return $result;
  222. }
  223. private static function successfulWithdrawLogs(Carbon $start, Carbon $end)
  224. {
  225. return DB::table('balance_logs as bl')
  226. ->join('withdraws as w', 'w.id', '=', 'bl.related_id')
  227. ->where('w.status', 1)
  228. ->where('bl.change_type', '提现')
  229. ->where('bl.amount', '<', 0)
  230. ->whereBetween('bl.created_at', [$start, $end]);
  231. }
  232. private static function rawAlias(string $alias): string
  233. {
  234. return '`' . str_replace('`', '``', DB::getTablePrefix() . $alias) . '`';
  235. }
  236. private static function item(string $key, string $label, $amount, $people): array
  237. {
  238. return [
  239. 'key' => $key,
  240. 'label' => $label,
  241. 'amount' => self::money((float)$amount),
  242. 'people' => (int)$people,
  243. ];
  244. }
  245. private static function sumItems(array $items): string
  246. {
  247. $total = array_reduce($items, static function (float $sum, array $item): float {
  248. return $sum + (float)$item['amount'];
  249. }, 0.0);
  250. return self::money($total);
  251. }
  252. private static function money(float $amount): string
  253. {
  254. return number_format($amount, 2, '.', '');
  255. }
  256. /**
  257. * @return array{0: Carbon, 1: Carbon}
  258. */
  259. private static function dateRange(string $startDate, string $endDate): array
  260. {
  261. $timezone = config('app.timezone', 'Asia/Shanghai');
  262. return [
  263. Carbon::createFromFormat('Y-m-d', $startDate, $timezone)->startOfDay(),
  264. Carbon::createFromFormat('Y-m-d', $endDate, $timezone)->endOfDay(),
  265. ];
  266. }
  267. }