From 8c9ac7b7f42372c2fbdcf493eff7d2d11606ad82 Mon Sep 17 00:00:00 2001 From: cody <648753004@qq.com> Date: Thu, 28 May 2026 14:23:15 +0800 Subject: [PATCH] update --- ...MigrateScheduleOverviewLegacySchedules.php | 150 ------------------ .../SyncAppointmentCarAppointments.php | 102 ++++++++++++ .../Commands/SyncCourseCarAppointments.php | 81 ++++++++++ app/Console/Kernel.php | 4 + app/Repositories/CarRepository.php | 7 +- 5 files changed, 191 insertions(+), 153 deletions(-) delete mode 100644 app/Console/Commands/MigrateScheduleOverviewLegacySchedules.php create mode 100644 app/Console/Commands/SyncAppointmentCarAppointments.php create mode 100644 app/Console/Commands/SyncCourseCarAppointments.php diff --git a/app/Console/Commands/MigrateScheduleOverviewLegacySchedules.php b/app/Console/Commands/MigrateScheduleOverviewLegacySchedules.php deleted file mode 100644 index 0e0403e..0000000 --- a/app/Console/Commands/MigrateScheduleOverviewLegacySchedules.php +++ /dev/null @@ -1,150 +0,0 @@ -option('year'); - $dryRun = (bool) $this->option('dry-run'); - - $query = ScheduleOverviewSchedule::query()->orderBy('id'); - if (!empty($year)) { - $query->where('year', (string) $year); - } - - $legacyList = $query->get(); - if ($legacyList->isEmpty()) { - $this->info('未找到需要迁移的数据。'); - return self::SUCCESS; - } - - $this->info('待迁移条数:' . $legacyList->count() . ($dryRun ? '(dry-run)' : '')); - - $createdGroups = 0; - $createdModules = 0; - $skippedModules = 0; - - DB::beginTransaction(); - try { - foreach ($legacyList as $legacy) { - $group = ScheduleOverviewScheduleGroup::query() - ->where('year', $legacy->year) - ->where('system_id', $legacy->system_id) - ->where('course_id', $legacy->course_id) - ->where('title', $legacy->title) - ->first(); - - if (empty($group)) { - $group = new ScheduleOverviewScheduleGroup(); - $group->year = (string) $legacy->year; - $group->system_id = (int) $legacy->system_id; - $group->course_id = (int) $legacy->course_id; - $group->title = (string) $legacy->title; - $group->admin_id = $legacy->admin_id; - $group->department_id = $legacy->department_id; - - if (!$dryRun) { - $group->save(); - } - $createdGroups++; - } - - $locationName = trim((string) ($legacy->location ?? '')); - $ownerName = trim((string) ($legacy->owner ?? '')); - - if ($locationName === '' || $ownerName === '') { - // 旧数据不完整时跳过,避免写入无法编辑的 module - $this->warn("跳过 legacy#{$legacy->id}:地点或负责人为空"); - continue; - } - - $location = ScheduleOverviewLocation::query()->where('name', $locationName)->first(); - if (empty($location)) { - $location = new ScheduleOverviewLocation(); - $location->name = $locationName; - $location->sort = 0; - $location->status = 1; - $location->admin_id = $legacy->admin_id; - $location->department_id = $legacy->department_id; - if (!$dryRun) { - $location->save(); - } - } - - $owner = ScheduleOverviewOwner::query()->where('name', $ownerName)->first(); - if (empty($owner)) { - $owner = new ScheduleOverviewOwner(); - $owner->name = $ownerName; - $owner->sort = 0; - $owner->status = 1; - $owner->admin_id = $legacy->admin_id; - $owner->department_id = $legacy->department_id; - if (!$dryRun) { - $owner->save(); - } - } - - $exists = ScheduleOverviewScheduleModule::query() - ->where('group_id', $group->id) - ->where('month', (int) $legacy->month) - ->where('location_id', $location->id) - ->where('owner_id', $owner->id) - ->where('count_text', (string) ($legacy->count_text ?? '')) - ->exists(); - - if ($exists) { - $skippedModules++; - continue; - } - - $module = new ScheduleOverviewScheduleModule(); - $module->group_id = $group->id; - $module->name = null; - $module->month = (int) $legacy->month; - $module->location_id = $location->id; - $module->owner_id = $owner->id; - $module->count_text = (string) ($legacy->count_text ?? ''); - $module->sort = 0; - $module->admin_id = $legacy->admin_id; - $module->department_id = $legacy->department_id; - - if (!$dryRun) { - $module->save(); - } - $createdModules++; - } - - if ($dryRun) { - DB::rollBack(); - } else { - DB::commit(); - } - } catch (\Throwable $throwable) { - DB::rollBack(); - $this->error($throwable->getMessage()); - return self::FAILURE; - } - - $this->info("迁移完成:新增 groups={$createdGroups},新增 modules={$createdModules},跳过重复 modules={$skippedModules}"); - if ($dryRun) { - $this->info('dry-run 未写入数据库。'); - } - - return self::SUCCESS; - } -} - diff --git a/app/Console/Commands/SyncAppointmentCarAppointments.php b/app/Console/Commands/SyncAppointmentCarAppointments.php new file mode 100644 index 0000000..eb471f6 --- /dev/null +++ b/app/Console/Commands/SyncAppointmentCarAppointments.php @@ -0,0 +1,102 @@ +where('status', 1) + ->where('start_time', '>', $now) + // 用户后续在个人资料里绑定了车牌,才有可能需要补发。 + ->whereHas('user', function ($query) { + $query->whereNotNull('plate') + ->where('plate', '!=', ''); + })->get(); + + foreach ($appointments as $appointment) { + // 用户当前车牌:以 users.plate 为准,表示用户后来补充/更新后的完整车牌列表。 + $userPlates = $this->parsePlates($appointment->user->plate); + // 预约记录车牌:表示当时提交预约时已经记录过的车牌列表。 + $appointmentPlates = $this->parsePlates($appointment->plate); + // 只找用户后来新增、但预约记录里没有的车牌,避免重复提交老车牌。 + $newPlates = array_values(array_diff($userPlates, $appointmentPlates)); + // 需要确认是否预约成功的车牌:以用户当前车牌为准。 + // 即使车牌已经回写到 appointments.plate,只要第三方没有成功记录,后续任务仍然应该继续补发。 + $pendingPlates = $userPlates; + + if (empty($newPlates) && empty($pendingPlates)) { + continue; + } + + if (!empty($newPlates)) { + // 把新增车牌回写到 appointments.plate,保持预约记录和用户当前车牌一致。 + // 注意:补发判断不能只依赖 appointments.plate,否则队列失败后会因为已回写而不再补发。 + $appointment->plate = implode(',', array_values(array_unique(array_merge($appointmentPlates, $newPlates)))); + $appointment->save(); + } + + foreach ($pendingPlates as $plate) { + // 第三方日志里如果已经有这个预约 + 车牌的成功记录,说明之前已经预约成功。 + // 这里再做一次幂等校验,防止手动补数据、任务重复执行或队列重试导致重复预约。 + $hasAppointment = ThirdAppointmentLog::where('appointment_id', $appointment->id) + ->where('plate', $plate) + ->where('plate_status', 1) + ->exists(); + + if ($hasAppointment) { + continue; + } + + // 没有成功记录才投递队列任务,由 SendAppointCar 调用第三方停车系统发起预约。 + dispatch(new SendAppointCar($appointment, $plate)); + $total++; + } + } + + $this->info("预约车牌同步完成,共提交 {$total} 条车牌预约"); + + return self::SUCCESS; + } + + /** + * 解析车牌字符串。 + * + * 数据库存储格式是多个车牌用英文逗号分隔,例如:苏E12345,苏E67890。 + * 这里统一做 trim、去空值、去重,兼容空字符串、null、以及用户输入时带空格的情况。 + */ + protected function parsePlates($plate) + { + return array_values(array_unique(array_filter(array_map('trim', explode(',', (string) $plate))))); + } +} diff --git a/app/Console/Commands/SyncCourseCarAppointments.php b/app/Console/Commands/SyncCourseCarAppointments.php new file mode 100644 index 0000000..a6bff28 --- /dev/null +++ b/app/Console/Commands/SyncCourseCarAppointments.php @@ -0,0 +1,81 @@ +where('status', 1) + ->whereHas('course', function ($query) use ($today) { + $query->where('status', 1) + ->whereNotNull('start_date') + ->where('start_date', '>', $today); + })->whereHas('user', function ($query) { + $query->whereNotNull('plate') + ->where('plate', '!=', ''); + })->get(); + + foreach ($courseSigns as $courseSign) { + // 2. 一个用户可能有多个车牌,按逗号拆开逐个处理 + $plates = $this->parsePlates($courseSign->user->plate); + + foreach ($plates as $plate) { + // 3. 如果这个车牌已经预约成功,就不用重复预约 + $hasAppointment = ThirdAppointmentLog::where('course_sign_id', $courseSign->id) + ->where('plate', $plate) + ->where('plate_status', 1) + ->exists(); + + if ($hasAppointment) { + continue; + } + + // 4. 没有预约成功记录,提交车牌预约任务 + dispatch(new SendCourseCar($courseSign, $plate)); + $total++; + } + } + + $this->info("课程车牌同步完成,共提交 {$total} 条车牌预约"); + + return self::SUCCESS; + } + + /** + * 解析用户车牌,兼容空格和重复车牌。 + */ + protected function parsePlates($plate) + { + return array_values(array_unique(array_filter(array_map('trim', explode(',', $plate))))); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index ec3984a..3c833e3 100755 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -33,6 +33,10 @@ class Kernel extends ConsoleKernel $schedule->command('update_user_no')->dailyAt('00:05'); // 更新课程校友资格 $schedule->command('auto_schoolmate')->everyThirtyMinutes(); + // 同步课程未预约的新增车牌号 + // $schedule->command('sync_course_car_appointments')->everyThirtyMinutes(); + // 同步预约未提交的新增车牌号 + $schedule->command('sync_appointment_car_appointments')->everyThirtyMinutes(); // 更新公司信息 // $schedule->command('update_company')->everyFiveMinutes(); // 全量同步公司信息(每天凌晨执行,不同步经纬度和地址) diff --git a/app/Repositories/CarRepository.php b/app/Repositories/CarRepository.php index 1c9aea5..9bbb6e0 100755 --- a/app/Repositories/CarRepository.php +++ b/app/Repositories/CarRepository.php @@ -76,7 +76,7 @@ class CarRepository 'plateNo' => $plateNo, 'type' => 1, 'addrId' => '4,6', - 'enterTime' => date('Y-m-d 00:00:01', strtotime($model->start_time)), + 'enterTime' => date('Y-m-d 00:00:00', strtotime($model->start_time)), 'leaveTime' => date('Y-m-d 23:59:58', strtotime($model->end_time)), // 失效时间 'failureTime' => date('Y-m-d 23:59:59', strtotime($model->end_time)), @@ -130,7 +130,7 @@ class CarRepository 'type' => 1, 'addrId' => '', 'enterTime' => $course->start_date . ' 00:00:00', - 'leaveTime' => $course->end_date . ' 00:00:00', + 'leaveTime' => $course->end_date . ' 59:59:58', 'failureTime' => date('Y-m-d 59:59:59', strtotime($course->end_date)), 'flag' => 1, 'cardType' => 100, @@ -147,6 +147,7 @@ class CarRepository if (empty($result['resCode'])) { $finally = 1; $order_no = json_decode($result['data'], true)['orderNo']; + $plate_status = 1; return true; } else { return false; @@ -156,7 +157,7 @@ class CarRepository return false; } finally { // 写日志 - $thirdAppointmentLogModel = ThirdAppointmentLog::add(0, $courseSigns->id, $courseSigns->user_id, 0, $url, $params, $result, $finally, '用户车位预订'); + $thirdAppointmentLogModel = ThirdAppointmentLog::add(0, $courseSigns->id, $courseSigns->user_id, $url, $params, $result, $finally, '用户车位预订'); // 写车牌记录信息 ThirdAppointmentLog::addPlate($thirdAppointmentLogModel, $plateNo, $plate_status, $order_no); }