with(['info']) ->where('id', $coachId) ->where('state', TechnicianStatus::ACTIVE->value) ->first(); abort_if(! $coach, 400, '技师不存在或未激活'); // 验证技师认证状态 abort_if(! $coach->info || $coach->info->state !== TechnicianAuthStatus::PASSED->value, 400, '技师未通过认证'); // 验证服务时间基本参数 $this->validateBasicServiceTime($serviceTime); // 验证技师工作时间 $workSchedule = $this->getCoachWorkSchedule($coachId); $this->validateWorkingHours($serviceTime, $workSchedule); // 验证时间冲突 $this->checkTimeConflicts($coachId, $serviceTime); } /** * 验证服务时间基本参数 * * 业务规则: * 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 { $schedule = CoachSchedule::where('coach_id', $coachId) ->where('state', 1) ->first(); if (! $schedule || empty($schedule->time_ranges)) { return [ 'work_days' => range(1, 7), 'work_hours' => [ 'start' => '09:00', 'end' => '21:00', ], 'rest_dates' => [], ]; } $timeRanges = json_decode($schedule->time_ranges, true); return [ 'work_days' => range(1, 7), // 默认每天都工作 'work_hours' => [ 'start' => $timeRanges[0]['start_time'], 'end' => end($timeRanges)['end_time'], ], 'rest_dates' => [], ]; } /** * 验证工作时间 * * 业务规则: * 1. 检查是否为工作日 * 2. 检查是否为特殊休息日 * 3. 检查是否在工作时间范围内 * * @param string $serviceTime 服务时间 * @param array $workSchedule 工作时间安排 * * @throws \Exception 验证失败时抛出异常 */ private function validateWorkingHours(string $serviceTime, array $workSchedule): void { $serviceDateTime = Carbon::parse($serviceTime); // 检查工作日 $dayOfWeek = $serviceDateTime->dayOfWeek; $workDays = $workSchedule['work_days'] ?? range(1, 7); // 如果未设置,默认每天都工作 abort_if( ! in_array($dayOfWeek, $workDays), 400, '该时间不在技师工作日内' ); // 检查特殊休息日 $dateStr = $serviceDateTime->format('Y-m-d'); $restDates = $workSchedule['rest_dates'] ?? []; // 如果未设置,默认没有休息日 abort_if( in_array($dateStr, $restDates), 400, '技师该日期休息' ); // 检查工作时间 $timeStr = $serviceDateTime->format('H:i'); $workHours = $workSchedule['work_hours'] ?? [ 'start' => '09:00', 'end' => '21:00', ]; // 如果未设置,使用默认工作时间 $startTime = Carbon::parse($workHours['start']); $endTime = Carbon::parse($workHours['end']); abort_if( $timeStr < $startTime->format('H:i') || $timeStr > $endTime->format('H:i'), 400, sprintf( '服务时间需在%s-%s之间', $startTime->format('H:i'), $endTime->format('H:i') ) ); } /** * 检查时间冲突 * * 业务规则: * 1. 检查已支付、已接单、服务中的订单 * 2. 考虑服务时长(默认2小时) * 3. 新订单的服务时间段不能与其他订单重叠 * * @param int $coachId 技师ID * @param string $serviceTime 服务时间 * * @throws \Exception 验证失败时抛出异常 */ private function checkTimeConflicts(int $coachId, string $serviceTime): void { $serviceDateTime = Carbon::parse($serviceTime); // 获取服务时长配置(默认2小时) $serviceDuration = config('business.default_service_duration', 120); // 计算本次服务的结束时间 $serviceEndTime = $serviceDateTime->copy()->addMinutes($serviceDuration); // 检查是否与其他订单时间冲突 $conflictingOrder = Order::query() ->where('coach_id', $coachId) ->where('service_time', '<=', $serviceEndTime) ->where('service_end_time', '>=', $serviceDateTime) ->whereIn('status', [ OrderStatus::PAID->value, OrderStatus::ACCEPTED->value, OrderStatus::SERVING->value, ]) ->first(); abort_if($conflictingOrder, 400, '该时间段已被预约'); } }