BackfillShareCommissions.php 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Models\BalanceLog;
  4. use App\Services\ShareCommissionRecorder;
  5. use Carbon\Carbon;
  6. use Illuminate\Console\Command;
  7. class BackfillShareCommissions extends Command
  8. {
  9. protected $signature = 'share:backfill-commissions
  10. {--start-date= : 返现入账起始日 Y-m-d,默认结束日前30天}
  11. {--end-date= : 返现入账结束日 Y-m-d,默认今天}
  12. {--chunk=500 : 每批读取条数,1至2000}
  13. {--write : 实际登记佣金明细;不传时只预览}';
  14. protected $description = '从已有充值返现流水补记分享佣金明细,不修改钱包或再次发奖';
  15. public function handle(ShareCommissionRecorder $recorder): int
  16. {
  17. try {
  18. $end = $this->parseDate((string)($this->option('end-date') ?: date('Y-m-d')));
  19. $start = $this->parseDate((string)($this->option('start-date') ?: $end->copy()->subDays(30)->toDateString()));
  20. if ($start->gt($end) || $start->diffInDays($end) > 366) {
  21. throw new \InvalidArgumentException('日期范围须先后有效,单次不超过366天');
  22. }
  23. $chunk = filter_var($this->option('chunk'), FILTER_VALIDATE_INT);
  24. if ($chunk === false || $chunk < 1 || $chunk > 2000) {
  25. throw new \InvalidArgumentException('chunk 必须是1至2000的整数');
  26. }
  27. $write = (bool)$this->option('write');
  28. $counts = ['scanned' => 0, 'created' => 0, 'pending' => 0, 'existing' => 0, 'skipped' => 0, 'incomplete_metadata' => 0];
  29. $reasons = [];
  30. $this->info(($write ? '写入登记' : '只读预览') . ":{$start->toDateString()} 至 {$end->toDateString()}");
  31. BalanceLog::where('change_type', '充值返现')->where('type', 1)->where('amount', '>', 0)
  32. ->whereBetween('created_at', [$start->toDateString() . ' 00:00:00', $end->toDateString() . ' 23:59:59'])
  33. ->chunkById($chunk, function ($rows) use ($recorder, $write, &$counts, &$reasons) {
  34. foreach ($rows as $row) {
  35. $result = $write ? $recorder->record($row) : $recorder->preview($row);
  36. $counts['scanned']++;
  37. $counts[$result['status']]++;
  38. if (!empty($result['incomplete_metadata'])) $counts['incomplete_metadata']++;
  39. if ($result['status'] === 'skipped') {
  40. $reason = $result['reason'];
  41. $reasons[$reason] = ($reasons[$reason] ?? 0) + 1;
  42. }
  43. }
  44. });
  45. $this->table(['项目', '条数'], array_map(fn ($key, $count) => [$key, $count], array_keys($counts), array_values($counts)));
  46. foreach ($reasons as $reason => $count) $this->line("跳过 {$reason}: {$count}");
  47. if (!$write) $this->info('预览未写入数据;确认后使用相同日期并加 --write 登记,钱包不会再次入账。');
  48. return self::SUCCESS;
  49. } catch (\Throwable $e) {
  50. $this->error($e->getMessage());
  51. return self::FAILURE;
  52. }
  53. }
  54. private function parseDate(string $value): Carbon
  55. {
  56. $date = Carbon::createFromFormat('!Y-m-d', $value, config('app.timezone', 'Asia/Shanghai'));
  57. if (!$date || $date->format('Y-m-d') !== $value) {
  58. throw new \InvalidArgumentException('日期必须为有效的 Y-m-d');
  59. }
  60. return $date;
  61. }
  62. }