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.

103 lines
4.1 KiB

1 week ago
<?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)))));
}
}