ProjectService.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. namespace App\Services\Client;
  3. use App\Models\Project;
  4. use App\Models\AgentInfo;
  5. use App\Models\CoachUser;
  6. use App\Enums\AgentStatus;
  7. use App\Enums\ProjectType;
  8. use App\Models\ProjectCate;
  9. use App\Enums\ProjectStatus;
  10. use App\Enums\AgentAuditStatus;
  11. use App\Enums\TechnicianStatus;
  12. use App\Enums\TechnicianAuthStatus;
  13. use Illuminate\Support\Facades\Cache;
  14. class ProjectService
  15. {
  16. /**
  17. * 获取项目列表
  18. * 根据区域代码和分类获取可用的项目列表
  19. *
  20. * @param string $areaCode 区域代码(6位数字)
  21. * @param int|null $projectCateId 项目分类ID
  22. * @param int $type 项目类型(1:普通项目,2:加钟项目)
  23. * @param int $perPage 每页数量
  24. * @return mixed
  25. */
  26. public function getProjectList($areaCode, $projectCateId = null, $type = ProjectType::VISIT, $perPage = 10)
  27. {
  28. // 生成缓存键
  29. $cacheKey = "project:list:{$areaCode}:{$projectCateId}:{$type}:{$perPage}";
  30. return Cache::remember($cacheKey, 3600, function () use ($areaCode, $projectCateId, $type, $perPage) {
  31. // 获取并验证项目分类
  32. $projectCate = ProjectCate::find($projectCateId);
  33. abort_if(!$projectCate, 404, '项目分类不存在');
  34. abort_if($projectCate->state != ProjectStatus::OPEN->value(), 404, '项目分类状态异常');
  35. // 查找区域代理商
  36. $agent = $this->findAvailableAgent($areaCode);
  37. // 获取项目列表
  38. return $this->getProjects($projectCate, $type, $agent, $perPage);
  39. });
  40. }
  41. /**
  42. * 如果代理商存在,则返回代理商项目,否则返回系统项目
  43. *
  44. * @param int $projectId 项目ID
  45. * @param string $areaCode 区域代码
  46. * @return Project 项目模型
  47. *
  48. * @throws \Illuminate\Http\Exceptions\HttpResponseException 项目不存在时抛出404异常
  49. */
  50. public function getProjectDetail($projectId, $areaCode)
  51. {
  52. // 查询系统项目
  53. $project = $this->getSystemProject($projectId);
  54. // 根据区域代码获取代理商
  55. $agent = $this->findAvailableAgent($areaCode);
  56. if ($agent) {
  57. $this->mergeAgentSettings($project, $agent);
  58. $project->agent_id = $agent->id;
  59. }
  60. return $project;
  61. }
  62. /**
  63. * 获取系统项目
  64. *
  65. * @param int $projectId 项目ID
  66. * @return Project
  67. * @throws \Illuminate\Http\Exceptions\HttpResponseException
  68. */
  69. private function getSystemProject($projectId)
  70. {
  71. $project = Project::where('state', ProjectStatus::OPEN->value())->find($projectId);
  72. abort_if(!$project, 404, '项目不存在');
  73. return $project;
  74. }
  75. /**
  76. * 获取技师开通的项目列表
  77. */
  78. public function getCoachProjectList($coachId, $areaCode, $projectCateId)
  79. {
  80. // 查询技师信息
  81. $coach = CoachUser::where('id', $coachId)
  82. ->where('state', TechnicianStatus::ACTIVE->value)
  83. ->with(['info', 'qual', 'real'])
  84. ->first();
  85. abort_if(! $coach, 404, '技师不存在');
  86. abort_if(! $coach->info, 404, '技师信息不存在');
  87. abort_if($coach->info->state !== TechnicianAuthStatus::PASSED->value, 404, '技师未通过审核');
  88. abort_if(! $coach->qual, 404, '技师资格证书不存在');
  89. abort_if($coach->qual->state !== TechnicianAuthStatus::PASSED->value, 404, '技师资格证书未通过审核');
  90. abort_if(! $coach->real, 404, '技师实名认证记录不存在');
  91. abort_if($coach->real->state !== TechnicianAuthStatus::PASSED->value, 404, '技师实名认证未通过审核');
  92. // 获取技师开通的项目ID列表
  93. $projectIds = $coach->projects()
  94. ->where('state', ProjectStatus::OPEN->value)
  95. ->pluck('project_id');
  96. // 根据区域代码获取代理商
  97. $agent = $this->findAvailableAgent($areaCode);
  98. if ($agent) {
  99. $agentCate = $agent->cates()
  100. ->where('project_cate_id', $projectCateId)
  101. ->first();
  102. $projectIds = $agentCate->projects()
  103. ->whereIn('project_id', $projectIds)
  104. ->pluck('project_id');
  105. }
  106. return $coach->projects()
  107. ->with('basicInfo')
  108. ->whereIn('project_id', $projectIds)
  109. ->get();
  110. }
  111. /**
  112. * 获取项目列表
  113. * 根据条件获取系统项目或代理商项目
  114. *
  115. * @param ProjectCate $projectCate 项目分类
  116. * @param int $type 项目类型
  117. * @param Agent|null $agent 代理商(可选)
  118. * @param int $perPage 每页数量
  119. * @return mixed
  120. */
  121. private function getProjects(ProjectCate $projectCate, int $type, $agent = null, int $perPage = 10)
  122. {
  123. // 构建基础查询
  124. $query = $projectCate->projects()
  125. ->where('state', ProjectStatus::OPEN->value());
  126. // 处理项目类型查询
  127. if ($type == ProjectType::OVERTIME->value()) {
  128. $query->whereIn('type', [
  129. ProjectType::VISIT->value(),
  130. ProjectType::OVERTIME->value()
  131. ]);
  132. } else {
  133. $query->where('type', ProjectType::VISIT->value());
  134. }
  135. // 如果有代理商,处理代理商项目设置
  136. if ($agent) {
  137. $agentCate = $agent->cates()
  138. ->where('project_cate_id', $projectCate->id)
  139. ->first();
  140. // 验证代理商分类状态
  141. abort_if(
  142. $agentCate && ($agentCate->state != ProjectStatus::OPEN->value()),
  143. 404,
  144. '当前区域未开通服务'
  145. );
  146. // 获取分页数据
  147. $projects = $query->paginate($perPage);
  148. // 处理代理商设置
  149. foreach ($projects as $project) {
  150. $this->mergeAgentSettings($project, $agent);
  151. }
  152. return $projects;
  153. }
  154. // 返回系统默认项目
  155. return $query->paginate($perPage);
  156. }
  157. /**
  158. * 递归查找可用的代理商
  159. * 从6位区域代码开始,每次去掉最后2位,直到找到代理商或只剩2位
  160. * 查找规则:
  161. * 1. 先查找精确匹配的区域代码
  162. * 2. 如果找不到,则去掉最后2位,查找上级区域
  163. * 3. 重复步骤2直到只剩2位区域代码
  164. * 4. 如果全部未找到则返回null
  165. *
  166. * @param string $areaCode 区域代码(2-6位数字)
  167. * @return Agent|null 返回找到的代理商或null
  168. * @throws \Exception 当查询过程中发生错误时抛出异常
  169. */
  170. private function findAvailableAgent($areaCode)
  171. {
  172. // 生成缓存键
  173. $cacheKey = "agent:area:{$areaCode}";
  174. return Cache::remember($cacheKey, 3600, function () use ($areaCode) {
  175. // 现有的查找逻辑
  176. $fullAreaCode = str_pad($areaCode, 6, '0', STR_PAD_RIGHT);
  177. $agent = AgentInfo::where('area_code', $fullAreaCode)
  178. ->where('state', AgentStatus::ENABLE->value)
  179. ->where('audit_state', AgentAuditStatus::PASSED->value)
  180. ->first();
  181. if ($agent) {
  182. return $agent;
  183. }
  184. if (strlen($areaCode) > 2) {
  185. $parentAreaCode = substr($areaCode, 0, -2);
  186. return $this->findAvailableAgent($parentAreaCode);
  187. }
  188. return null;
  189. });
  190. }
  191. /**
  192. * 合并代理商项目设置
  193. * 将代理商的自定义设置覆盖到项目默认设置中
  194. *
  195. * @param Project $project 项目对象
  196. * @param Agent $agent 代理商对象
  197. */
  198. private function mergeAgentSettings(&$project, $agent)
  199. {
  200. // 查找代理商对该项目的设置
  201. $agentProject = $agent->projects()
  202. ->where('project_id', $project->id)
  203. ->first();
  204. // 如果有设置则覆盖默认值
  205. if ($agentProject) {
  206. $project->price = $agentProject->price ?? $project->price;
  207. $project->duration = $agentProject->duration ?? $project->duration;
  208. $project->distance = $agentProject->distance ?? $project->distance;
  209. }
  210. }
  211. }