|
@@ -2,11 +2,12 @@
|
|
|
|
|
|
namespace App\Services\Coach;
|
|
|
|
|
|
-use App\Enums\ProjectStatus;
|
|
|
-use App\Enums\TechnicianStatus;
|
|
|
-use App\Models\MemberUser;
|
|
|
use App\Models\Project;
|
|
|
+use App\Models\CoachUser;
|
|
|
+use App\Models\MemberUser;
|
|
|
+use App\Enums\ProjectStatus;
|
|
|
use App\Models\SettingGroup;
|
|
|
+use App\Enums\TechnicianStatus;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
@@ -15,123 +16,127 @@ class ProjectService
|
|
|
/**
|
|
|
* 获取所有可以开通的项目列表
|
|
|
*
|
|
|
- * @param int $userId 技师用户ID
|
|
|
+ * 业务流程:
|
|
|
+ * 1. 获取所有状态为开放的项目
|
|
|
+ * 2. 通过关联查询检查项目开通状态
|
|
|
+ * 3. 返回项目列表
|
|
|
+ *
|
|
|
+ * 注意事项:
|
|
|
+ * - 只返回状态为开放的项目
|
|
|
+ * - 已开通的项目必须是状态正常的
|
|
|
+ * - 使用 leftJoin 优化查询性能
|
|
|
+ * - 在数据库层面计算开通状态文本
|
|
|
+ *
|
|
|
+ * @param CoachUser $coach 技师对象
|
|
|
+ * @return array 项目列表,每个项目包含:
|
|
|
+ * - id: int 项目ID
|
|
|
+ * - title: string 项目标题
|
|
|
+ * - subtitle: string|null 项目副标题
|
|
|
+ * - cover: string 项目封面图
|
|
|
+ * - price: decimal 项目价格
|
|
|
+ * - original_price: decimal 原价
|
|
|
+ * - sales: int 销量
|
|
|
+ * - duration: int 服务时长(分钟)
|
|
|
+ * - project_desc: string 项目描述
|
|
|
+ * - service_desc: string 服务说明
|
|
|
+ * - type: int 项目类型
|
|
|
+ * - is_opened: bool 是否已开通
|
|
|
+ * - opened_text: string 开通状态文本(已开通|未开通)
|
|
|
*/
|
|
|
- public function getAvailableProjects(int $userId): array
|
|
|
+ public function getAvailableProjects(CoachUser $coach): array
|
|
|
{
|
|
|
- try {
|
|
|
- // 加载用户和技师信息
|
|
|
- $user = MemberUser::with(['coach', 'coach.projects'])->findOrFail($userId);
|
|
|
- abort_if(! $user->coach, 404, '技师信息不存在');
|
|
|
-
|
|
|
- // 获取所有可开通的项目
|
|
|
- $projects = Project::where('state', ProjectStatus::OPEN->value)
|
|
|
- ->select([
|
|
|
- 'id',
|
|
|
- 'title',
|
|
|
- 'subtitle',
|
|
|
- 'cover',
|
|
|
- 'price',
|
|
|
- 'original_price',
|
|
|
- 'sales',
|
|
|
- 'duration',
|
|
|
- 'project_desc',
|
|
|
- 'service_desc',
|
|
|
- 'type',
|
|
|
- ])
|
|
|
- ->get();
|
|
|
-
|
|
|
- // 记录日志
|
|
|
- Log::info('获取可开通项目列表成功', [
|
|
|
- 'user_id' => $userId,
|
|
|
- 'coach_id' => $user->coach->id,
|
|
|
- 'project_count' => $projects->count(),
|
|
|
- ]);
|
|
|
-
|
|
|
- return [
|
|
|
- 'items' => $projects,
|
|
|
- 'total' => $projects->count(),
|
|
|
- ];
|
|
|
-
|
|
|
- } catch (\Exception $e) {
|
|
|
- Log::error('获取可开通项目列表失败', [
|
|
|
- 'user_id' => $userId,
|
|
|
- 'error' => $e->getMessage(),
|
|
|
- 'file' => $e->getFile(),
|
|
|
- 'line' => $e->getLine(),
|
|
|
- ]);
|
|
|
- throw $e;
|
|
|
- }
|
|
|
+ // 使用 leftJoin 优化查询,避免 N+1 问题
|
|
|
+ return Project::where('project.state', ProjectStatus::OPEN->value)
|
|
|
+ ->leftJoin('coach_project', function ($join) use ($coach) {
|
|
|
+ $join->on('project.id', '=', 'coach_project.project_id')
|
|
|
+ ->where('coach_project.coach_id', '=', $coach->id)
|
|
|
+ ->where('coach_project.state', '=', ProjectStatus::OPEN->value);
|
|
|
+ })
|
|
|
+ ->select([
|
|
|
+ 'project.id',
|
|
|
+ 'project.title',
|
|
|
+ 'project.subtitle',
|
|
|
+ 'project.cover',
|
|
|
+ 'project.price',
|
|
|
+ 'project.original_price',
|
|
|
+ 'project.sales',
|
|
|
+ 'project.duration',
|
|
|
+ 'project.project_desc',
|
|
|
+ 'project.service_desc',
|
|
|
+ 'project.type',
|
|
|
+ DB::raw('CASE WHEN coach_project.id IS NOT NULL THEN true ELSE false END as is_opened'),
|
|
|
+ DB::raw('CASE WHEN coach_project.id IS NOT NULL THEN "已开通" ELSE "未开通" END as opened_text'),
|
|
|
+ ])
|
|
|
+ ->get()
|
|
|
+ ->toArray();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 技师开通项目
|
|
|
+ * 技师开通/关闭项目
|
|
|
*
|
|
|
- * @param int $userId 技师用户ID
|
|
|
- * @param array $data 开通项目数据
|
|
|
+ * 业务流程:
|
|
|
+ * 1. 验证技师状态
|
|
|
+ * 2. 验证项目状态
|
|
|
+ * 3. 根据操作类型处理项目
|
|
|
+ * 4. 返回操作结果
|
|
|
+ *
|
|
|
+ * @param CoachUser $coach 技师对象
|
|
|
+ * @param array $data 项目操作数据,包含:
|
|
|
+ * - project_id: int 项目ID
|
|
|
+ * - action: string 操作类型(open:开通 close:关闭)
|
|
|
+ * @return array 操作结果,包含:
|
|
|
+ * - project_id: int 项目ID
|
|
|
+ * - project_name: string 项目名称
|
|
|
+ * - state: int 项目状态
|
|
|
+ * - state_text: string 状态描述
|
|
|
+ * @throws \Illuminate\Http\Exceptions\HttpResponseException 当验证失败时抛出异常
|
|
|
*/
|
|
|
- public function openProject(int $userId, array $data): array
|
|
|
+ public function openProject(CoachUser $coach, array $data): array
|
|
|
{
|
|
|
- return DB::transaction(function () use ($userId, $data) {
|
|
|
- try {
|
|
|
- // 加载用户和技师信息
|
|
|
- $user = MemberUser::with(['coach', 'coach.projects'])->findOrFail($userId);
|
|
|
- abort_if(! $user->coach, 404, '技师信息不存在');
|
|
|
+ return DB::transaction(function () use ($coach, $data) {
|
|
|
+ // 检查技师状态是否正常
|
|
|
+ $coach->validateActiveStatus('技师状态异常,无法操作项目');
|
|
|
|
|
|
- // 验证项目是否存在且状态正常
|
|
|
- $project = Project::where('id', $data['project_id'])
|
|
|
- ->where('state', ProjectStatus::OPEN->value)
|
|
|
- ->first();
|
|
|
- abort_if(! $project, 404, '项目不存在或已下架');
|
|
|
-
|
|
|
- // 检查是否已开通该项目
|
|
|
- $existingProject = $user->coach->projects()
|
|
|
- ->where('project_id', $data['project_id'])
|
|
|
- ->first();
|
|
|
- abort_if($existingProject, 422, '已开通该项目');
|
|
|
+ // 验证项目是否存在且状态为开放
|
|
|
+ $project = Project::validateOpenProject($data['project_id']);
|
|
|
|
|
|
- // 检查技师状态
|
|
|
- abort_if($user->coach->state !== TechnicianStatus::ACTIVE->value,
|
|
|
- 422, '技师状态异常,无法开通项目');
|
|
|
+ // 根据操作类型处理项目并获取状态
|
|
|
+ $state = match ($data['action']) {
|
|
|
+ 'open' => $this->handleProjectOpen($coach, $data['project_id']),
|
|
|
+ 'close' => $this->handleProjectClose($coach, $data['project_id']),
|
|
|
+ };
|
|
|
|
|
|
- // 创建技师项目关联
|
|
|
- $coachProject = $user->coach->projects()->create([
|
|
|
- 'project_id' => $data['project_id'],
|
|
|
- 'state' => ProjectStatus::OPEN->value,
|
|
|
- 'discount_amount' => 0.00,
|
|
|
- 'service_gender' => 0,
|
|
|
- 'service_distance' => 0,
|
|
|
- 'traffic_fee_type' => 2,
|
|
|
- 'traffic_fee' => 0,
|
|
|
- 'created_at' => now(),
|
|
|
- 'updated_at' => now(),
|
|
|
- ]);
|
|
|
-
|
|
|
- // 记录日志
|
|
|
- Log::info('技师开通项目成功', [
|
|
|
- 'user_id' => $userId,
|
|
|
- 'coach_id' => $user->coach->id,
|
|
|
- 'project_id' => $data['project_id'],
|
|
|
- 'project_name' => $project->name,
|
|
|
- ]);
|
|
|
+ // 返回格式化的操作结果
|
|
|
+ return $project->formatActionResult($state);
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- return [
|
|
|
- 'message' => '项目开通成功',
|
|
|
- 'project_id' => $project->id,
|
|
|
- 'project_name' => $project->name,
|
|
|
- ];
|
|
|
+ /**
|
|
|
+ * 处理项目开通
|
|
|
+ *
|
|
|
+ * @param CoachUser $coach 技师对象
|
|
|
+ * @param int $projectId 项目ID
|
|
|
+ * @return int 返回开通后的状态值
|
|
|
+ */
|
|
|
+ private function handleProjectOpen(CoachUser $coach, int $projectId): int
|
|
|
+ {
|
|
|
+ // 创建或更新项目关联为开通状态
|
|
|
+ $coach->updateOrCreateProjectRelation($projectId, ProjectStatus::OPEN->value);
|
|
|
+ return ProjectStatus::OPEN->value;
|
|
|
+ }
|
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
- Log::error('技师开通项目失败', [
|
|
|
- 'user_id' => $userId,
|
|
|
- 'data' => $data,
|
|
|
- 'error' => $e->getMessage(),
|
|
|
- 'file' => $e->getFile(),
|
|
|
- 'line' => $e->getLine(),
|
|
|
- ]);
|
|
|
- throw $e;
|
|
|
- }
|
|
|
- });
|
|
|
+ /**
|
|
|
+ * 处理项目关闭
|
|
|
+ *
|
|
|
+ * @param CoachUser $coach 技师对象
|
|
|
+ * @param int $projectId 项目ID
|
|
|
+ * @return int 返回关闭后的状态值
|
|
|
+ */
|
|
|
+ private function handleProjectClose(CoachUser $coach, int $projectId): int
|
|
|
+ {
|
|
|
+ // 创建或更新项目关联为关闭状态
|
|
|
+ $coach->updateOrCreateProjectRelation($projectId, ProjectStatus::CLOSE->value);
|
|
|
+ return ProjectStatus::CLOSE->value;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -170,7 +175,7 @@ class ProjectService
|
|
|
abort_if(! $voucherSettingItem, 404, '代金卷设置不存在');
|
|
|
|
|
|
// 从项目设置中验证代金卷金额的合理范围
|
|
|
- abort_if($data['voucher'] > $voucherSettingItem->max_value, 422, '代金卷金额不能超过'.$voucherSettingItem->max_value.'元');
|
|
|
+ abort_if($data['voucher'] > $voucherSettingItem->max_value, 422, '代金卷金额不能超过' . $voucherSettingItem->max_value . '元');
|
|
|
|
|
|
// 更新技师折扣金额配置
|
|
|
$user->coach->settingValues()->updateOrCreate(
|
|
@@ -187,7 +192,7 @@ class ProjectService
|
|
|
abort_if(! $trafficFeeSettingItem, 404, '路费设置不存在');
|
|
|
|
|
|
// 从项目设置中验证交通费类型的合理范围
|
|
|
- abort_if($data['traffic_fee'] > $trafficFeeSettingItem->max_value, 422, '交通费类型不能超过'.$trafficFeeSettingItem->max_value.'元');
|
|
|
+ abort_if($data['traffic_fee'] > $trafficFeeSettingItem->max_value, 422, '交通费类型不能超过' . $trafficFeeSettingItem->max_value . '元');
|
|
|
|
|
|
// 更新技师路费配置
|
|
|
$user->coach->settingValues()->updateOrCreate(
|
|
@@ -204,7 +209,7 @@ class ProjectService
|
|
|
abort_if(! $customerGenderSettingItem, 404, '顾客性别设置不存在');
|
|
|
|
|
|
// 从项目设置中验证顾客性别设置的合理范围
|
|
|
- abort_if($data['gender'] > $customerGenderSettingItem->max_value, 422, '顾客性别设置不能超过'.$customerGenderSettingItem->max_value.'元');
|
|
|
+ abort_if($data['gender'] > $customerGenderSettingItem->max_value, 422, '顾客性别设置不能超过' . $customerGenderSettingItem->max_value . '元');
|
|
|
|
|
|
// 更新技师顾客性别设置
|
|
|
$user->coach->settingValues()->updateOrCreate(
|
|
@@ -228,7 +233,6 @@ class ProjectService
|
|
|
'coach_id' => $user->coach->id,
|
|
|
'settings' => $data,
|
|
|
];
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('技师项目设置更新失败', [
|
|
|
'user_id' => $userId,
|