123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- <?php
- namespace App\Services\Client\Traits;
- use Carbon\Carbon;
- use App\Models\Order;
- use App\Models\CoachUser;
- use App\Enums\OrderStatus;
- use App\Models\CoachSchedule;
- use App\Enums\TechnicianStatus;
- use App\Enums\TechnicianAuthStatus;
- use Illuminate\Support\Facades\Log;
- /**
- * 服务时间验证 Trait
- *
- * 业务规则:
- * 1. 技师状态验证:
- * - 技师必须存在且状态为激活
- * - 技师必须通过认证
- *
- * 2. 服务时间验证:
- * - 服务时间不能早于当前时间
- * - 最多只能提前7天预约
- * - 必须在技师的工作时间内
- * - 不能与其他订单时间冲突
- *
- * 3. 工作时间规则:
- * - 默认工作时间为 09:00-21:00
- * - 可以通过排班设置自定义工作时间
- * - 支持设置特殊休息日
- *
- * 4. 订单冲突规则:
- * - 检查已支付、已接单、服务中的订单
- * - 考虑服务时长(默认2小时)
- * - 开始时间不能在其他订单的服务时间段内
- * - 结束时间不能在其他订单的服务时间段内
- */
- trait ValidatesServiceTime
- {
- /**
- * 验证服务时间
- *
- * @param int $coachId 技师ID
- * @param string $serviceTime 服务时间(格式:Y-m-d H:i:s)
- * @param int $duration 服务时长(分钟)
- * @return bool
- *
- * @throws \Exception 验证失败时抛出异常
- */
- public function validateServiceTime(int $coachId, string $serviceTime, int $duration): bool
- {
- try {
- Log::info('开始验证服务时间', [
- 'coach_id' => $coachId,
- 'service_time' => $serviceTime,
- 'duration' => $duration
- ]);
- // 验证基本参数
- Log::info('开始验证基本参数');
- $this->validateServiceTimeParams($coachId, $serviceTime, $duration);
- Log::info('基本参数验证通过');
- Log::info('服务时间验证通过');
- return true;
- } catch (\Exception $e) {
- Log::error('验证服务时间失败', [
- 'coach_id' => $coachId,
- 'service_time' => $serviceTime,
- 'duration' => $duration,
- 'error_message' => $e->getMessage(),
- 'error_code' => $e->getCode(),
- 'error_file' => $e->getFile(),
- 'error_line' => $e->getLine(),
- 'error_trace' => $e->getTraceAsString()
- ]);
- throw $e;
- }
- }
- /**
- * 验证服务时间参数
- *
- * @param int $coachId 技师ID
- * @param string $serviceTime 服务时间(格式:Y-m-d H:i:s)
- * @param int $duration 服务时长(分钟)
- *
- * @throws \Exception 验证失败时抛出异常
- */
- private function validateServiceTimeParams(int $coachId, string $serviceTime, int $duration): void
- {
- Log::info('开始验证技师状态', ['coach_id' => $coachId]);
- // 验证技师状态
- $coach = CoachUser::query()
- ->with(['info'])
- ->where('id', $coachId)
- ->where('state', TechnicianStatus::ACTIVE->value)
- ->first();
- abort_if(! $coach, 400, '技师不存在或未激活');
- Log::info('技师状态验证通过', ['coach' => $coach->toArray()]);
- // 验证技师认证状态
- Log::info('开始验证技师认证状态');
- abort_if(
- ! $coach->info || $coach->info->state !== TechnicianAuthStatus::PASSED->value,
- 400,
- '技师未通过认证'
- );
- Log::info('技师认证状态验证通过');
- // 验证服务时间基本参数
- Log::info('开始验证服务时间基本参数', ['service_time' => $serviceTime]);
- $this->validateBasicServiceTime($serviceTime);
- Log::info('服务时间基本参数验证通过');
- // 验证技师工作时间
- Log::info('开始验证技师工作时间');
- $workSchedule = $this->getCoachWorkSchedule($coachId);
- Log::info('获取到技师排班信息', ['work_schedule' => $workSchedule]);
- $serviceDateTime = Carbon::parse($serviceTime);
- $serviceEndTime = $serviceDateTime->copy()->addMinutes($duration);
- Log::info('计算服务时间', [
- 'service_start' => $serviceDateTime->format('Y-m-d H:i:s'),
- 'service_end' => $serviceEndTime->format('Y-m-d H:i:s'),
- 'duration' => $duration
- ]);
- $this->validateWorkingHours($serviceDateTime, $serviceEndTime, $workSchedule);
- Log::info('技师工作时间验证通过');
- // 验证时间冲突
- Log::info('开始验证时间冲突');
- $this->checkTimeConflicts($coachId, $serviceDateTime, $serviceEndTime);
- Log::info('时间冲突验证通过');
- }
- /**
- * 验证服务时间基本参数
- *
- * 业务规则:
- * 1. 服务时间不能早于当前时间
- * 2. 最多只能提前7天预约
- *
- * @param string $serviceTime 服务时间
- *
- * @throws \Exception 验证失败时抛出异常
- */
- private function validateBasicServiceTime(string $serviceTime): void
- {
- $serviceDateTime = Carbon::parse($serviceTime);
- // 验证是否过期
- abort_if(
- $serviceDateTime->isPast(),
- 400,
- '服务时间不能早于当前时间'
- );
- // 验证预约时间范围(最多提前7天)
- $maxAdvanceDays = config('business.max_advance_days', 7);
- abort_if(
- $serviceDateTime->diffInDays(now()) > $maxAdvanceDays,
- 400,
- "最多只能提前{$maxAdvanceDays}天预约"
- );
- }
- /**
- * 获取技师工作时间安排
- *
- * 业务规则:
- * 1. 优先使用技师的自定义排班
- * 2. 如果没有排班,使用默认时间(09:00-21:00)
- * 3. 默认每天都工作
- *
- * @param int $coachId 技师ID
- * @return array 工作时间安排
- */
- private function getCoachWorkSchedule(int $coachId): array
- {
- Log::info('开始获取技师排班信息', ['coach_id' => $coachId]);
- $schedule = CoachSchedule::where('coach_id', $coachId)
- ->where('state', 1)
- ->first();
- Log::info('查询技师排班记录', [
- 'coach_id' => $coachId,
- 'has_schedule' => !is_null($schedule),
- 'schedule' => $schedule ? $schedule->toArray() : null
- ]);
- if (! $schedule) {
- $defaultSchedule = [
- 'work_days' => range(1, 7),
- 'work_hours' => [
- [
- 'start' => '09:00',
- 'end' => '21:00',
- ]
- ],
- 'rest_dates' => [],
- ];
- Log::info('未找到排班记录,使用默认排班', ['default_schedule' => $defaultSchedule]);
- return $defaultSchedule;
- }
- $timeRanges = is_string($schedule->time_ranges)
- ? json_decode($schedule->time_ranges, true)
- : $schedule->time_ranges;
- Log::info('解析排班时间范围', [
- 'raw_time_ranges' => $schedule->time_ranges,
- 'parsed_time_ranges' => $timeRanges
- ]);
- // 从排班表中获取工作日
- $workDays = [];
- $workHours = [];
- foreach ($timeRanges as $index => $range) {
- Log::info('处理时间范围', [
- 'index' => $index,
- 'range' => $range
- ]);
- $workHours[] = [
- 'start' => $range['start_time'],
- 'end' => $range['end_time'],
- ];
- Log::info('更新工作时间', [
- 'current_work_hours' => $workHours
- ]);
- }
- $workDays = array_unique($workDays);
- Log::info('处理工作日', [
- 'initial_work_days' => $workDays
- ]);
- // 如果没有设置工作日,则默认每天都工作
- if (empty($workDays)) {
- $workDays = range(1, 7);
- Log::info('未设置工作日,使用默认每天工作', [
- 'default_work_days' => $workDays
- ]);
- }
- $result = [
- 'work_days' => $workDays,
- 'work_hours' => $workHours,
- ];
- Log::info('获取技师排班信息完成', [
- 'final_schedule' => $result
- ]);
- return $result;
- }
- /**
- * 验证是否在工作时间内
- */
- private function validateWorkingHours(Carbon $serviceDateTime, Carbon $serviceEndTime, array $workSchedule): void
- {
- $isInSchedule = false;
- foreach ($workSchedule['work_hours'] as $range) {
- $rangeStart = Carbon::parse($serviceDateTime->format('Y-m-d') . ' ' . $range['start']);
- $rangeEnd = Carbon::parse($serviceDateTime->format('Y-m-d') . ' ' . $range['end']);
- if (
- $serviceDateTime->between($rangeStart, $rangeEnd)
- ) {
- $isInSchedule = true;
- break;
- }
- }
- abort_if(!$isInSchedule, 400, '所选时间不在技师服务时间范围内');
- }
- /**
- * 检查订单时间冲突
- */
- private function checkTimeConflicts(int $coachId, Carbon $serviceDateTime, Carbon $serviceEndTime): void
- {
- // 检查是否与其他订单时间重叠
- $conflictingOrders = Order::where('coach_id', $coachId)
- ->where(function ($query) use ($serviceDateTime, $serviceEndTime) {
- $query->where(function ($q) use ($serviceDateTime, $serviceEndTime) {
- $q->where('service_start_time', '<=', $serviceEndTime->format('Y-m-d H:i:s'))
- ->where('service_end_time', '>=', $serviceDateTime->format('Y-m-d H:i:s'));
- });
- })
- ->whereIn('state', [
- OrderStatus::ACCEPTED->value,
- OrderStatus::DEPARTED->value,
- OrderStatus::ARRIVED->value,
- OrderStatus::SERVICING->value,
- ])
- ->exists();
- abort_if($conflictingOrders, 400, '该时间段技师已有其他预约');
- }
- }
|