RewardRuleService.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. <?php
  2. namespace App\Services;
  3. use App\Services\Sharing\ShareDefaults;
  4. use App\Support\MaintenancePolicy;
  5. use Illuminate\Support\Facades\DB;
  6. use Illuminate\Validation\Rule;
  7. use Illuminate\Validation\ValidationException;
  8. use InvalidArgumentException;
  9. /** Per-category tiers for 笔笔返 (rebate) and 回水 (backflow); each save replaces one category's list. */
  10. class RewardRuleService
  11. {
  12. private const TABLES = ['rebate' => 'rebate_rules', 'backflow' => 'backflow_rules'];
  13. public static function categories(string $type = 'rebate'): array
  14. {
  15. $categories = ShareDefaults::categories();
  16. if ($type === 'backflow') {
  17. unset($categories['self:lottery']);
  18. foreach (ConfigService::sortedList('game_list') as $item) {
  19. if (!is_string($item['game'] ?? null)) continue;
  20. $category = MaintenancePolicy::lotterySubcategory($item);
  21. if ($category === null || isset($categories[$category])) continue;
  22. $label = is_string($item['name'] ?? null) ? trim($item['name']) : '';
  23. $categories[$category] = $label === '' ? $category : $label;
  24. }
  25. }
  26. return $categories;
  27. }
  28. public static function get(string $type): array
  29. {
  30. $table = self::table($type);
  31. $categories = $rules = [];
  32. foreach (self::categories($type) as $value => $label) {
  33. $categories[] = ['value' => $value, 'label' => $label];
  34. $rules[$value] = [];
  35. }
  36. $query = DB::table($table)->orderBy('category');
  37. if ($type === 'rebate') $query->orderBy('level')->orderBy('min_effective_amount');
  38. else $query->orderBy('min_loss_amount');
  39. foreach ($query->get() as $row) {
  40. if (!array_key_exists($row->category, $rules)) continue;
  41. $rules[$row->category][] = self::present($type, $row);
  42. }
  43. $result = ['type' => $type, 'categories' => $categories, 'rules' => $rules];
  44. if ($type === 'rebate') {
  45. $result['levels'] = DB::table('level')->orderBy('level')->get(['level', 'level_name'])
  46. ->map(fn ($row) => ['level' => (int) $row->level, 'level_name' => (string) $row->level_name])->all();
  47. }
  48. return $result;
  49. }
  50. public static function save(string $type, array $params): array
  51. {
  52. $table = self::table($type);
  53. $plain = 'regex:/^\d{1,12}(?:\.\d{1,4})?$/D';
  54. $money = 'regex:/^\d{1,12}(?:\.\d{1,2})?$/D';
  55. $amount = ['required', 'numeric', $money, 'min:0'];
  56. $rules = ['category' => ['required', 'string', Rule::in(array_keys(self::categories($type)))],
  57. 'rules' => ['present', 'array', 'max:200'],
  58. 'rules.*' => ['array'],
  59. 'rules.*.turnover_multiple' => ['required', 'numeric', $money, 'min:0', 'max:999999']];
  60. if ($type === 'rebate') {
  61. $rules['rules.*.level'] = ['required', 'integer', Rule::in(DB::table('level')->pluck('level')->map(fn ($v) => (int) $v)->all())];
  62. $rules['rules.*.min_effective_amount'] = $amount;
  63. $rules['rules.*.percentage'] = ['required', 'numeric', $plain, 'min:0', 'max:100'];
  64. } else {
  65. $rules['rules.*.min_loss_amount'] = $amount;
  66. $rules['rules.*.reward_amount'] = $amount;
  67. }
  68. $params = validator($params, $rules, [], ['rules.*.level' => '会员等级', 'rules.*.percentage' => '返回百分比',
  69. 'rules.*.min_effective_amount' => '有效投注', 'rules.*.min_loss_amount' => '亏损金额',
  70. 'rules.*.reward_amount' => '奖励金额', 'rules.*.turnover_multiple' => '需要打码量'])->validate();
  71. $rows = $seen = [];
  72. $now = now();
  73. foreach ($params['rules'] as $rule) {
  74. $row = ['category' => $params['category'], 'turnover_multiple' => self::decimal($rule['turnover_multiple']),
  75. 'created_at' => $now, 'updated_at' => $now];
  76. if ($type === 'rebate') {
  77. $row += ['level' => (int) $rule['level'], 'min_effective_amount' => self::decimal($rule['min_effective_amount']),
  78. 'percentage' => bcadd((string) $rule['percentage'], '0', 4)];
  79. $key = $row['level'] . '|' . $row['min_effective_amount'];
  80. } else {
  81. $row += ['min_loss_amount' => self::decimal($rule['min_loss_amount']),
  82. 'reward_amount' => self::decimal($rule['reward_amount'])];
  83. $key = $row['min_loss_amount'];
  84. }
  85. if (isset($seen[$key])) {
  86. throw ValidationException::withMessages(['rules' => $type === 'rebate'
  87. ? '同一会员等级的有效投注门槛不能重复' : '亏损金额门槛不能重复']);
  88. }
  89. $seen[$key] = true;
  90. $rows[] = $row;
  91. }
  92. DB::transaction(function () use ($table, $params, $rows) {
  93. DB::table($table)->where('category', $params['category'])->delete();
  94. if ($rows) DB::table($table)->insert($rows);
  95. });
  96. return self::get($type);
  97. }
  98. /** Rules of one type grouped by category, highest threshold first, for settlement. */
  99. public static function tiers(string $type): array
  100. {
  101. $tiers = [];
  102. $threshold = $type === 'rebate' ? 'min_effective_amount' : 'min_loss_amount';
  103. $query = DB::table(self::table($type))->orderByDesc($threshold);
  104. if ($type === 'backflow') $query->where('category', '<>', 'self:lottery');
  105. foreach ($query->get() as $row) {
  106. $tiers[$row->category][] = self::present($type, $row);
  107. }
  108. return $tiers;
  109. }
  110. private static function present(string $type, object $row): array
  111. {
  112. $data = ['id' => (int) $row->id, 'category' => (string) $row->category];
  113. if ($type === 'rebate') {
  114. $data += ['level' => (int) $row->level, 'min_effective_amount' => self::decimal($row->min_effective_amount),
  115. 'percentage' => bcadd((string) $row->percentage, '0', 4)];
  116. } else {
  117. $data += ['min_loss_amount' => self::decimal($row->min_loss_amount), 'reward_amount' => self::decimal($row->reward_amount)];
  118. }
  119. return $data + ['turnover_multiple' => self::decimal($row->turnover_multiple)];
  120. }
  121. private static function decimal($value): string
  122. {
  123. return bcadd((string) $value, '0', 2);
  124. }
  125. private static function table(string $type): string
  126. {
  127. if (!isset(self::TABLES[$type])) throw new InvalidArgumentException('奖励类型错误');
  128. return self::TABLES[$type];
  129. }
  130. }