date((string) ($this->argument('date') ?: now()->toDateString())); if (!$end) return self::FAILURE; if ($this->option('all')) { $start = $this->earliestDate(); if ($start === null) { $this->info('没有任何会员数据,无需补算'); return self::SUCCESS; } $start = Carbon::parse($start)->startOfDay(); } elseif ($this->option('from')) { $start = $this->date((string) $this->option('from')); if (!$start) return self::FAILURE; } else { $start = $end->copy()->subDays(max(1, min(31, (int) $this->option('days'))) - 1); } if ($start->gt($end)) { $this->error('开始日期不能晚于结束日期'); return self::FAILURE; } $total = (int) $start->diffInDays($end) + 1; if ($total > 1) $this->info("重算 {$start->toDateString()} ~ {$end->toDateString()},共 {$total} 天"); $rows = 0; // 从最近的一天往前算:补算过程中最新的数据先可用 for ($day = $end->copy(), $i = 1; $day->gte($start); $day->subDay(), ++$i) { $count = $reports->refresh($day->toDateString()); $rows += $count; $this->info(($total > 1 ? "[{$i}/{$total}] " : '') . $day->toDateString() . ':' . $count . ' 行'); } if ($total > 1) $this->info("完成,共写入 {$rows} 行"); return self::SUCCESS; } private function date(string $value): ?Carbon { $parsed = Carbon::createFromFormat('!Y-m-d', $value); if (!$parsed || $parsed->format('Y-m-d') !== $value) { $this->error('日期必须是有效的 Y-m-d:' . $value); return null; } return $parsed; } /** 最早有会员数据的日期:会员注册、充值、提现、三方充提、资金流水、已结算注单中最早的一天。 */ private function earliestDate(): ?string { $dates = []; foreach ([['users', 'created_at'], ['recharges', 'created_at'], ['withdraws', 'created_at'], ['payment_orders', 'created_at'], ['balance_logs', 'created_at'], ['share_bet_facts', 'settled_at']] as [$table, $column]) { if (!Schema::hasTable($table)) continue; $min = DB::table($table)->min($column); if ($min !== null && $min !== '') $dates[] = substr((string) $min, 0, 10); } return $dates === [] ? null : min($dates); } }