123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- <?php
- namespace App\Services\Client\Traits;
- use App\Enums\OrderStatus;
- use App\Enums\TechnicianAuthStatus;
- use App\Enums\TechnicianStatus;
- use App\Models\CoachSchedule;
- use App\Models\CoachUser;
- use App\Models\Order;
- use Carbon\Carbon;
- /**
- * 服务时间验证 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)
- *
- * @throws \Exception 验证失败时抛出异常
- */
- private function validateServiceTimeParams(int $coachId, string $serviceTime): void
- {
- // 验证技师状态
- $coach = CoachUser::query()
- ->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, '该时间段已被预约');
- }
- }
|