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