|
@@ -2,16 +2,16 @@
|
|
|
|
|
|
namespace App\Services\Coach;
|
|
|
|
|
|
-use App\Enums\OrderStatus;
|
|
|
-use App\Enums\TechnicianAuthStatus;
|
|
|
-use App\Enums\TechnicianLocationType;
|
|
|
-use App\Enums\TechnicianWorkStatus;
|
|
|
-use App\Models\CoachSchedule;
|
|
|
use App\Models\CoachUser;
|
|
|
+use App\Enums\OrderStatus;
|
|
|
use App\Models\MemberUser;
|
|
|
-use Illuminate\Support\Facades\Cache;
|
|
|
+use App\Models\CoachSchedule;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
+use App\Enums\TechnicianAuthStatus;
|
|
|
+use App\Enums\TechnicianWorkStatus;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
+use App\Enums\TechnicianLocationType;
|
|
|
+use Illuminate\Support\Facades\Cache;
|
|
|
use Illuminate\Support\Facades\Redis;
|
|
|
|
|
|
class AccountService
|
|
@@ -27,14 +27,11 @@ class AccountService
|
|
|
{
|
|
|
DB::beginTransaction();
|
|
|
try {
|
|
|
- $this->setTransactionConfig();
|
|
|
|
|
|
abort_if(! $user->coach, 404, '技师信息不存在');
|
|
|
|
|
|
// 检查是否有待审核的记录
|
|
|
- $pendingRecord = $user->coach->infoRecords()
|
|
|
- ->where('state', TechnicianAuthStatus::AUDITING->value)
|
|
|
- ->exists();
|
|
|
+ $pendingRecord = $this->hasPendingRecord($user->coach, 'info');
|
|
|
abort_if($pendingRecord, 422, '已有待审核的基本信息记录');
|
|
|
|
|
|
// 创建技师信息
|
|
@@ -50,7 +47,6 @@ class AccountService
|
|
|
$this->logInfo('技师提交基本信息成功', $user, $data);
|
|
|
|
|
|
return ['message' => '基本信息提交成功'];
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
$this->logError('提交技师基本信息失败', $user, $data, $e);
|
|
@@ -60,38 +56,72 @@ class AccountService
|
|
|
|
|
|
/**
|
|
|
* 提交技师资质信息
|
|
|
+ * 包括资质证书照片、营业执照和健康证照片的提交和审核
|
|
|
+ *
|
|
|
+ * 业务流程:
|
|
|
+ * 1. 验证技师信息存在性
|
|
|
+ * 2. 检查是否有待审核的记录
|
|
|
+ * 3. 创建新的资质审核记录
|
|
|
+ * 4. 清除相关缓存
|
|
|
+ *
|
|
|
+ * 注意事项:
|
|
|
+ * - 同一时间只能有一条待审核记录
|
|
|
+ * - 审核不通过可以重新提交
|
|
|
+ * - 所有图片数据不限制格式
|
|
|
+ *
|
|
|
+ * @param User $user 当前认证用户
|
|
|
+ * @param array $data 资质信息数据,包含:
|
|
|
+ * - qual_type: string 资质类型(如:高级按摩师)
|
|
|
+ * - qual_photo: string 资质证书照片
|
|
|
+ * - business_license: string 营业执照照片
|
|
|
+ * - health_cert: string 健康证照片
|
|
|
+ * @return array 返回结果,包含:
|
|
|
+ * - message: string 提示信息
|
|
|
+ * - data: array 详细数据
|
|
|
+ * - record_id: int 记录ID
|
|
|
+ * - state: int 状态值
|
|
|
+ * - state_text: string 状态文本
|
|
|
+ * @throws \Exception 当验证失败或保存失败时抛出异常
|
|
|
*/
|
|
|
public function submitQualification($user, array $data)
|
|
|
{
|
|
|
+ // 开启数据库事务
|
|
|
DB::beginTransaction();
|
|
|
try {
|
|
|
- $this->setTransactionConfig();
|
|
|
-
|
|
|
- abort_if(! $user->coach, 404, '技师信息不存在');
|
|
|
-
|
|
|
- // 检查是否有待审核的记录
|
|
|
- $pendingRecord = $user->coach->qualRecords()
|
|
|
- ->where('state', TechnicianAuthStatus::AUDITING->value)
|
|
|
- ->exists();
|
|
|
- abort_if($pendingRecord, 422, '已有待审核的资质信息记录');
|
|
|
-
|
|
|
- // 创建资质信息
|
|
|
- $record = $user->coach->qualRecords()->create(array_merge($data, [
|
|
|
- 'state' => TechnicianAuthStatus::AUDITING->value,
|
|
|
- ]));
|
|
|
+ // 验证技师信息是否存在
|
|
|
+ abort_if(!$user->coach, 404, '技师信息不存在');
|
|
|
+
|
|
|
+ // 检查是否有待审核的记录,避免重复提交
|
|
|
+ $pendingRecord = $this->hasPendingRecord($user->coach, 'qual');
|
|
|
+ // abort_if($pendingRecord, 422, '已有待审核的资质信息记录');
|
|
|
+
|
|
|
+ // 创建新的资质审核记录,设置为待审核状态
|
|
|
+ $record = $user->coach->qualRecords()->create([
|
|
|
+ 'qual_type' => $data['qual_type'],
|
|
|
+ 'qual_photo' => $data['qual_photo'],
|
|
|
+ 'business_license' => $data['business_license'],
|
|
|
+ 'health_cert' => $data['health_cert'],
|
|
|
+ 'state' => TechnicianAuthStatus::AUDITING->value
|
|
|
+ ]);
|
|
|
|
|
|
- // 清除技师信息缓存
|
|
|
+ // 清除技师信息缓存,确保数据一致性
|
|
|
$this->clearCoachCache($user->coach->id);
|
|
|
|
|
|
+ // 提交事务
|
|
|
DB::commit();
|
|
|
|
|
|
- $this->logInfo('技师提交资质信息成功', $user, $data);
|
|
|
-
|
|
|
- return ['message' => '资质信息提交成功'];
|
|
|
-
|
|
|
+ // 返回成功结果
|
|
|
+ return [
|
|
|
+ 'message' => '资质信息提交成功',
|
|
|
+ 'data' => [
|
|
|
+ 'record_id' => $record->id,
|
|
|
+ 'state' => TechnicianAuthStatus::AUDITING->value,
|
|
|
+ 'state_text' => TechnicianAuthStatus::fromValue(TechnicianAuthStatus::AUDITING->value)->label(),
|
|
|
+ ]
|
|
|
+ ];
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 发生异常时回滚事务
|
|
|
DB::rollBack();
|
|
|
- $this->logError('提交技师资质信息失败', $user, $data, $e);
|
|
|
throw $e;
|
|
|
}
|
|
|
}
|
|
@@ -103,14 +133,11 @@ class AccountService
|
|
|
{
|
|
|
DB::beginTransaction();
|
|
|
try {
|
|
|
- $this->setTransactionConfig();
|
|
|
|
|
|
abort_if(! $user->coach, 404, '技师信息不存在');
|
|
|
|
|
|
// 检查是否有待审核的记录
|
|
|
- $pendingRecord = $user->coach->realRecords()
|
|
|
- ->where('state', TechnicianAuthStatus::AUDITING->value)
|
|
|
- ->exists();
|
|
|
+ $pendingRecord = $this->hasPendingRecord($user->coach, 'real');
|
|
|
abort_if($pendingRecord, 422, '已有待审核的实名认证信息');
|
|
|
|
|
|
// 创建实名认证信息
|
|
@@ -126,7 +153,6 @@ class AccountService
|
|
|
$this->logInfo('技师提交实名认证信息成功', $user, $this->maskSensitiveData($data));
|
|
|
|
|
|
return ['message' => '实名认证信息提交成功'];
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
$this->logError('提交实名认证信息失败', $user, $this->maskSensitiveData($data), $e);
|
|
@@ -144,7 +170,7 @@ class AccountService
|
|
|
abort_if(! $user->coach, 404, '技师信息不存在');
|
|
|
|
|
|
return Cache::remember(
|
|
|
- self::CACHE_KEY_PREFIX.$user->coach->id,
|
|
|
+ self::CACHE_KEY_PREFIX . $user->coach->id,
|
|
|
self::CACHE_TTL,
|
|
|
function () use ($user) {
|
|
|
return $this->fetchCoachInfo($user->coach);
|
|
@@ -156,15 +182,6 @@ class AccountService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置事务配置
|
|
|
- */
|
|
|
- private function setTransactionConfig()
|
|
|
- {
|
|
|
- DB::statement('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED');
|
|
|
- DB::statement('SET SESSION innodb_lock_wait_timeout=10');
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 记录信息日志
|
|
|
*/
|
|
@@ -275,7 +292,7 @@ class AccountService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 身份证号脱敏
|
|
|
+ * 身份证��脱敏
|
|
|
*/
|
|
|
private function maskIdCard($idCard)
|
|
|
{
|
|
@@ -302,7 +319,7 @@ class AccountService
|
|
|
*/
|
|
|
private function clearCoachCache($coachId)
|
|
|
{
|
|
|
- Cache::forget(self::CACHE_KEY_PREFIX.$coachId);
|
|
|
+ Cache::forget(self::CACHE_KEY_PREFIX . $coachId);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -331,7 +348,7 @@ class AccountService
|
|
|
}
|
|
|
|
|
|
// 生成Redis键
|
|
|
- $key = $coachId.'_'.$type;
|
|
|
+ $key = $coachId . '_' . $type;
|
|
|
|
|
|
// 将置信息写入Redis
|
|
|
$result = Redis::geoadd('coach_locations', $longitude, $latitude, $key);
|
|
@@ -356,7 +373,6 @@ class AccountService
|
|
|
]);
|
|
|
|
|
|
return $result;
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
Log::error('技师位置信息设置异常', [
|
|
@@ -402,7 +418,6 @@ class AccountService
|
|
|
]);
|
|
|
|
|
|
return $result;
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('获取技师常用位置信息异常', [
|
|
|
'coach_id' => $user->coach->id ?? null,
|
|
@@ -466,7 +481,6 @@ class AccountService
|
|
|
'updated_at' => $schedule->updated_at->toDateTimeString(),
|
|
|
],
|
|
|
];
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('技师排班设置败', [
|
|
|
'user_id' => $userId,
|
|
@@ -489,13 +503,19 @@ class AccountService
|
|
|
|
|
|
// 验证每个时间段格式并转换为分钟数进行比较
|
|
|
$ranges = collect($timeRanges)->map(function ($range) {
|
|
|
- abort_if(! isset($range['start_time'], $range['end_time']),
|
|
|
- 400, '时间段格式错误');
|
|
|
+ abort_if(
|
|
|
+ ! isset($range['start_time'], $range['end_time']),
|
|
|
+ 400,
|
|
|
+ '时间段格式错误'
|
|
|
+ );
|
|
|
|
|
|
// 验证时间格式
|
|
|
foreach (['start_time', 'end_time'] as $field) {
|
|
|
- abort_if(! preg_match('/^([01][0-9]|2[0-3]):[0-5][0-9]$/', $range[$field]),
|
|
|
- 400, '时间格式错,应为HH:mm格式');
|
|
|
+ abort_if(
|
|
|
+ ! preg_match('/^([01][0-9]|2[0-3]):[0-5][0-9]$/', $range[$field]),
|
|
|
+ 400,
|
|
|
+ '时间格式错,应为HH:mm格式'
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
// 转换为分钟数便于比较
|
|
@@ -503,8 +523,11 @@ class AccountService
|
|
|
$endMinutes = $this->timeToMinutes($range['end_time']);
|
|
|
|
|
|
// 验证时间先后
|
|
|
- abort_if($startMinutes >= $endMinutes,
|
|
|
- 400, "时间段 {$range['start_time']}-{$range['end_time']} 结束时间必须大于开始时间");
|
|
|
+ abort_if(
|
|
|
+ $startMinutes >= $endMinutes,
|
|
|
+ 400,
|
|
|
+ "时间段 {$range['start_time']}-{$range['end_time']} 结束时间必须大于开始时间"
|
|
|
+ );
|
|
|
|
|
|
return [
|
|
|
'start_time' => $range['start_time'],
|
|
@@ -520,13 +543,16 @@ class AccountService
|
|
|
$ranges->each(function ($range, $index) use ($ranges) {
|
|
|
if ($index > 0) {
|
|
|
$prevRange = $ranges[$index - 1];
|
|
|
- abort_if($range['start_minutes'] <= $prevRange['end_minutes'],
|
|
|
- 400, "时间段 {$prevRange['start_time']}-{$prevRange['end_time']} 和 ".
|
|
|
- "{$range['start_time']}-{$range['end_time']} 之间存在重叠");
|
|
|
+ abort_if(
|
|
|
+ $range['start_minutes'] <= $prevRange['end_minutes'],
|
|
|
+ 400,
|
|
|
+ "时间段 {$prevRange['start_time']}-{$prevRange['end_time']} 和 " .
|
|
|
+ "{$range['start_time']}-{$range['end_time']} 之间存在重叠"
|
|
|
+ );
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 返回排序后的时间段,只保留需要的字段
|
|
|
+ // 返回排序后的时间,只保留需要的字段
|
|
|
return $ranges->map(function ($range) {
|
|
|
return [
|
|
|
'start_time' => $range['start_time'],
|
|
@@ -561,7 +587,6 @@ class AccountService
|
|
|
|
|
|
// 清除相关的可预约时间段缓存
|
|
|
$this->clearTimeSlotCache($coachId);
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('更新排班缓存失败', [
|
|
|
'coach_id' => $coachId,
|
|
@@ -645,7 +670,7 @@ class AccountService
|
|
|
TechnicianWorkStatus::FREE->value;
|
|
|
}
|
|
|
|
|
|
- // 如果状态没有变化,则不需要更新
|
|
|
+ // 如���状态没有变,则不需要更新
|
|
|
if ($currentStatus === $newStatus) {
|
|
|
DB::rollBack();
|
|
|
|
|
@@ -687,7 +712,6 @@ class AccountService
|
|
|
'updated_at' => now()->toDateTimeString(),
|
|
|
],
|
|
|
];
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
Log::error('技师工作状态更新失败', [
|
|
@@ -707,18 +731,27 @@ class AccountService
|
|
|
{
|
|
|
// 验证基本信息认证
|
|
|
$baseInfo = $coach->info;
|
|
|
- abort_if(! $baseInfo || $baseInfo->state !== TechnicianAuthStatus::PASSED->value,
|
|
|
- 422, '基本信息未认证通过');
|
|
|
+ abort_if(
|
|
|
+ ! $baseInfo || $baseInfo->state !== TechnicianAuthStatus::PASSED->value,
|
|
|
+ 422,
|
|
|
+ '基本信息未认证通过'
|
|
|
+ );
|
|
|
|
|
|
// 验证资质认证
|
|
|
$qualification = $coach->qual;
|
|
|
- abort_if(! $qualification || $qualification->state !== TechnicianAuthStatus::PASSED->value,
|
|
|
- 422, '资质信息未认证通过');
|
|
|
+ abort_if(
|
|
|
+ ! $qualification || $qualification->state !== TechnicianAuthStatus::PASSED->value,
|
|
|
+ 422,
|
|
|
+ '资质信息未认证通过'
|
|
|
+ );
|
|
|
|
|
|
// 验证实名认证
|
|
|
$realName = $coach->real;
|
|
|
- abort_if(! $realName || $realName->state !== TechnicianAuthStatus::PASSED->value,
|
|
|
- 422, '实名信息未认证通过');
|
|
|
+ abort_if(
|
|
|
+ ! $realName || $realName->state !== TechnicianAuthStatus::PASSED->value,
|
|
|
+ 422,
|
|
|
+ '实名信息未认证通过'
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -784,7 +817,6 @@ class AccountService
|
|
|
];
|
|
|
|
|
|
Redis::setex($cacheKey, 86400, json_encode($cacheData));
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('更新工作状态缓存失败', [
|
|
|
'coach_id' => $coachId,
|
|
@@ -815,7 +847,6 @@ class AccountService
|
|
|
'work_status_text' => TechnicianWorkStatus::fromValue($workStatus)->label(),
|
|
|
'updated_at' => now()->toDateTimeString(),
|
|
|
];
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('获取技师工作状态失败', [
|
|
|
'coach_id' => $coachId,
|
|
@@ -866,7 +897,6 @@ class AccountService
|
|
|
]);
|
|
|
|
|
|
return $result;
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('获取技师排班信息失败', [
|
|
|
'user_id' => $userId,
|
|
@@ -876,4 +906,44 @@ class AccountService
|
|
|
throw $e;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证技师基础信息
|
|
|
+ *
|
|
|
+ * @param User $user 用户对象
|
|
|
+ * @param bool $throwError 是否抛出异常
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ private function validateBasicCoach($user, bool $throwError = true): bool
|
|
|
+ {
|
|
|
+ if (!$user || !$user->coach) {
|
|
|
+ if ($throwError) {
|
|
|
+ abort_if(!$user, 404, '用户不存在');
|
|
|
+ abort_if(!$user->coach, 404, '技师信息不存在');
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否存在待审核记录
|
|
|
+ *
|
|
|
+ * @param CoachUser $coach 技师对象
|
|
|
+ * @param string $type 记录类型(info|qual|real)
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ private function hasPendingRecord($coach, string $type): bool
|
|
|
+ {
|
|
|
+ $method = match ($type) {
|
|
|
+ 'info' => 'infoRecords',
|
|
|
+ 'qual' => 'qualRecords',
|
|
|
+ 'real' => 'realRecords',
|
|
|
+ default => throw new \InvalidArgumentException('Invalid record type')
|
|
|
+ };
|
|
|
+
|
|
|
+ return $coach->{$method}()
|
|
|
+ ->where('state', TechnicianAuthStatus::AUDITING->value)
|
|
|
+ ->exists();
|
|
|
+ }
|
|
|
}
|