master
cody 1 week ago
parent 295c89c4d4
commit 8c9ac7b7f4

@ -1,150 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\ScheduleOverviewLocation;
use App\Models\ScheduleOverviewOwner;
use App\Models\ScheduleOverviewSchedule;
use App\Models\ScheduleOverviewScheduleGroup;
use App\Models\ScheduleOverviewScheduleModule;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class MigrateScheduleOverviewLegacySchedules extends Command
{
protected $signature = 'schedule-overview:migrate-legacy-schedules {--year=} {--dry-run}';
protected $description = '将旧的 schedule_overview_schedules 数据迁移到 schedule_groups + modules不删除旧数据';
public function handle(): int
{
$year = $this->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;
}
}

@ -0,0 +1,102 @@
<?php
namespace App\Console\Commands;
use App\Jobs\SendAppointCar;
use App\Models\Appointment;
use App\Models\ThirdAppointmentLog;
use Illuminate\Console\Command;
class SyncAppointmentCarAppointments extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sync_appointment_car_appointments';
/**
* The console command description.
*
* @var string
*/
protected $description = '同步预约未提交的新增车牌号';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$now = date('Y-m-d H:i:s');
$total = 0;
// 只处理还没开始的、已审核通过的预约。
// 车牌预约是发给第三方停车系统的,过期预约不需要补发;未审核通过的预约也不能提前发起车牌预约。
$appointments = Appointment::with('user')
->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)))));
}
}

@ -0,0 +1,81 @@
<?php
namespace App\Console\Commands;
use App\Jobs\SendCourseCar;
use App\Models\CourseSign;
use App\Models\ThirdAppointmentLog;
use Illuminate\Console\Command;
class SyncCourseCarAppointments extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sync_course_car_appointments';
/**
* The console command description.
*
* @var string
*/
protected $description = '同步课程未预约的新增车牌号';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$today = date('Y-m-d');
$total = 0;
// 1. 找出:审核通过、课程已发布、课程填写了开始日期且未到开始时间、用户填写了车牌号的报名记录
$courseSigns = CourseSign::with(['course', 'user'])
->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)))));
}
}

@ -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();
// 全量同步公司信息(每天凌晨执行,不同步经纬度和地址)

@ -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);
}

Loading…
Cancel
Save