You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
5.3 KiB

4 days ago
<?php
namespace App\Console\Commands;
use App\Models\Activity;
use App\Models\ActivityDay;
use App\Models\Reservation;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
/**
* 按活动的开始日~结束日,为每一天(活动日)生成若干条测试预约记录。
* 若某日尚无 activity_days 记录,会自动创建场次并设置足够放票数。
*/
class SeedActivityReservationsByDayCommand extends Command
{
protected $signature = 'reservations:seed-activity-days
{activity_id=1 : 活动 ID}
{--per-day=3 : 每个活动日生成几条预约}
{--dry-run : 只打印计划,不写入数据库}';
protected $description = '按活动起止日期为每个活动日生成测试预约activity_id + activity_day_id';
public function handle(): int
{
$activityId = (int) $this->argument('activity_id');
$perDay = max(1, (int) $this->option('per-day'));
$dryRun = (bool) $this->option('dry-run');
$activity = Activity::query()->find($activityId);
if (!$activity) {
$this->error("未找到活动 id={$activityId}");
return self::FAILURE;
}
if (!$activity->start_at || !$activity->end_at) {
$this->error('该活动缺少 start_at / end_at无法按日期遍历。请先在后台为活动设置开始、结束时间。');
return self::FAILURE;
}
$start = $activity->start_at->copy()->startOfDay();
$end = $activity->end_at->copy()->startOfDay();
$this->info("活动 #{$activity->id} {$activity->title}");
$this->line("场馆 venue_id={$activity->venue_id}");
$this->line("日期范围:{$start->toDateString()} {$end->toDateString()}(含首尾)");
$this->line("每日生成预约:{$perDay} 条".($dryRun ? 'dry-run' : ''));
$days = [];
for ($d = $start->copy(); $d->lte($end); $d->addDay()) {
$days[] = $d->copy();
}
$this->table(
['序号', '活动日', '将生成条数'],
collect($days)->map(fn (Carbon $c, $i) => [$i + 1, $c->toDateString(), $perDay])->all()
);
if ($dryRun) {
$this->warn('已跳过写入(--dry-run。去掉该参数后执行将写入数据库。');
return self::SUCCESS;
}
$created = 0;
DB::transaction(function () use ($activity, $days, $perDay, &$created) {
$dayIndex = 0;
foreach ($days as $day) {
$dateStr = $day->toDateString();
$dayIndex++;
$activityDay = ActivityDay::query()
->where('activity_id', $activity->id)
->whereDate('activity_date', $dateStr)
->first();
if (!$activityDay) {
$opensAt = $day->copy()->subDays(7)->setTime(9, 0, 0);
$activityDay = ActivityDay::create([
'activity_id' => $activity->id,
'activity_date' => $dateStr,
'day_quota' => max($perDay * 2, 30),
'booked_count' => 0,
'opens_at' => $opensAt,
]);
$this->line("已创建活动日场次:{$dateStr}activity_day_id={$activityDay->id}");
}
$ticketEach = 1;
$needBooked = $activityDay->booked_count + ($perDay * $ticketEach);
if ($activityDay->day_quota < $needBooked) {
$activityDay->day_quota = $needBooked + 10;
$activityDay->save();
$this->line("已调高 {$dateStr} 放票数至 {$activityDay->day_quota}");
}
for ($i = 1; $i <= $perDay; $i++) {
$phone = $this->makeUniquePhone($activity->id, $dayIndex, $i);
$res = Reservation::create([
'venue_id' => $activity->venue_id,
'activity_id' => $activity->id,
'activity_day_id' => $activityDay->id,
'visitor_name' => "测试用户{$dateStr}-{$i}",
'visitor_phone' => $phone,
'qr_token' => (string) Str::uuid(),
'status' => 'pending',
'ticket_count' => $ticketEach,
'reservation_source' => 'legacy',
]);
$created++;
$activityDay->increment('booked_count', $ticketEach);
$this->line(" + 预约 #{$res->id} {$phone} → activity_day {$activityDay->id}");
}
}
Activity::refreshRegisteredCountFromReservations($activity->id);
});
$this->info("完成:新建 {$created} 条预约;活动 registered_count 已按已预约人数合计更新。");
return self::SUCCESS;
}
private function makeUniquePhone(int $activityId, int $dayIndex, int $i): string
{
// 11 位手机号138 + 8 位数字,保证脚本内不易重复
$n = ($activityId * 10000 + $dayIndex * 100 + $i) % 100000000;
return '138'.str_pad((string) $n, 8, '0', STR_PAD_LEFT);
}
}