OrderService.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. <?php
  2. namespace App\Services\Coach;
  3. use App\Enums\OrderStatus;
  4. use App\Enums\TechnicianAuthStatus;
  5. use App\Enums\TechnicianLocationType;
  6. use App\Enums\TechnicianStatus;
  7. use App\Models\MemberUser;
  8. use App\Models\Order;
  9. use App\Services\SettingItemService;
  10. use Illuminate\Support\Facades\DB;
  11. use Illuminate\Support\Facades\Log;
  12. use Illuminate\Support\Facades\Redis;
  13. class OrderService
  14. {
  15. private SettingItemService $settingService;
  16. public function __construct(SettingItemService $settingService)
  17. {
  18. $this->settingService = $settingService;
  19. }
  20. /**
  21. * 获取可抢订单列表
  22. *
  23. * @param int $userId 技师用户ID
  24. * @param array $params 请求参数
  25. * @return \Illuminate\Pagination\LengthAwarePaginator
  26. */
  27. public function getGrabList(int $userId, array $params)
  28. {
  29. try {
  30. // 加载用户和技师信息
  31. $user = MemberUser::with([
  32. 'coach',
  33. 'coach.info',
  34. 'coach.real',
  35. 'coach.qual',
  36. 'coach.locations',
  37. 'coach.projects',
  38. ])->findOrFail($userId);
  39. // 验证技师信息
  40. [$coach, $location] = $this->validateCoach($user);
  41. // 获取技师项目信息
  42. $coachProjects = $coach->projects;
  43. $coachProjectIds = $coachProjects->pluck('project_id')->toArray();
  44. // 获取系统配置
  45. $settings = $this->getSystemSettings();
  46. // 获取附近订单
  47. $orderDistances = $this->getNearbyOrders($location);
  48. // 构建订单查询
  49. $query = $this->buildOrderQuery($coachProjectIds, $orderDistances);
  50. // 处理订单数据
  51. $this->processOrderData($query, $orderDistances, $coachProjects, $settings);
  52. // 分页获取数据
  53. return $query->paginate(
  54. $params['per_page'] ?? 10,
  55. ['*'],
  56. 'page',
  57. $params['page'] ?? 1
  58. );
  59. } catch (\Exception $e) {
  60. Log::error('获取可抢订单列表失败', [
  61. 'user_id' => $userId,
  62. 'params' => $params,
  63. 'error' => $e->getMessage(),
  64. 'trace' => $e->getTraceAsString(),
  65. ]);
  66. throw $e;
  67. }
  68. }
  69. /**
  70. * 验证技师信息
  71. */
  72. private function validateCoach(MemberUser $user): array
  73. {
  74. $coach = $user->coach;
  75. abort_if(! $coach, 404, '技师不存在');
  76. abort_if(! $coach->info, 404, '技师信息不存在');
  77. abort_if($coach->info->state != TechnicianStatus::ACTIVE->value, 404, '技师状态异常');
  78. abort_if($coach->real->state != TechnicianAuthStatus::PASSED->value, 404, '技师实名认证未通过');
  79. abort_if($coach->qual->state != TechnicianAuthStatus::PASSED->value, 404, '技师资质认证未通过');
  80. $location = $coach->locations()
  81. ->where('type', TechnicianLocationType::COMMON->value)
  82. ->first();
  83. abort_if(! $location, 404, '技师定位地址不存在');
  84. return [$coach, $location];
  85. }
  86. /**
  87. * 获取系统配置
  88. */
  89. private function getSystemSettings(): array
  90. {
  91. return [
  92. 'coach_income' => $this->settingService->getItemDetail('coach_income')->default_value ?? 0,
  93. 'min_distance' => $this->settingService->getItemDetail('qibugongli')->default_value ?? 0,
  94. 'base_price' => $this->settingService->getItemDetail('qibujia')->default_value ?? 0,
  95. 'per_km_price' => $this->settingService->getItemDetail('meigonglijiage')->default_value ?? 0,
  96. ];
  97. }
  98. /**
  99. * 获取附近订单
  100. */
  101. private function getNearbyOrders($location, float $maxDistance = 40): array
  102. {
  103. $nearbyOrders = Redis::georadius(
  104. 'order_locations',
  105. $location->longitude,
  106. $location->latitude,
  107. $maxDistance,
  108. 'km',
  109. ['WITHCOORD', 'WITHDIST']
  110. );
  111. if (! $nearbyOrders) {
  112. Log::warning('获取附近订单失败', [
  113. 'location' => [
  114. 'longitude' => $location->longitude,
  115. 'latitude' => $location->latitude,
  116. ],
  117. ]);
  118. return [];
  119. }
  120. $orderDistances = [];
  121. foreach ($nearbyOrders as $order) {
  122. $orderId = str_replace('order:', '', $order[0]);
  123. $distance = round($order[1], 1);
  124. $orderDistances[$orderId] = $distance;
  125. }
  126. return $orderDistances;
  127. }
  128. /**
  129. * 构建订单查询
  130. */
  131. private function buildOrderQuery(array $coachProjectIds, array $orderDistances)
  132. {
  133. return Order::query()
  134. ->select([
  135. 'id',
  136. 'order_no',
  137. 'project_id',
  138. 'location',
  139. 'address',
  140. 'latitude',
  141. 'longitude',
  142. 'service_time',
  143. 'created_at',
  144. 'agent_id',
  145. DB::raw('0 as distance'),
  146. ])
  147. ->with(['project', 'agent.projects'])
  148. ->where('state', OrderStatus::CREATED)
  149. ->whereIn('project_id', $coachProjectIds)
  150. ->whereIn('id', array_keys($orderDistances))
  151. ->orderBy('created_at', 'desc');
  152. }
  153. /**
  154. * 处理订单数据
  155. */
  156. private function processOrderData($query, array $orderDistances, $coachProjects, array $settings): void
  157. {
  158. $query->get()->each(function ($order) use ($orderDistances, $coachProjects, $settings) {
  159. // 设置距离
  160. $order->distance = $orderDistances[$order->id];
  161. // 处理代理商项目价格
  162. $this->processAgentPrice($order);
  163. // 处理技师项目信息
  164. $this->processCoachProject($order, $coachProjects, $settings);
  165. });
  166. }
  167. /**
  168. * 处理代理商价格
  169. */
  170. private function processAgentPrice($order): void
  171. {
  172. if ($order->agent_id && $order->agent && $order->agent->projects) {
  173. $agentProject = $order->agent->projects
  174. ->where('id', $order->project_id)
  175. ->where('state', 1)
  176. ->first();
  177. if ($agentProject) {
  178. $order->project->price = $agentProject->agent_price;
  179. $order->project->duration = $agentProject->duration;
  180. }
  181. }
  182. }
  183. /**
  184. * 处理技师项目信息
  185. */
  186. private function processCoachProject($order, $coachProjects, array $settings): void
  187. {
  188. $coachProject = $coachProjects->where('project_id', $order->project_id)->first();
  189. if (! $coachProject) {
  190. return;
  191. }
  192. // 计算技师佣金
  193. $order->project->coach_income = round($order->project->price * $settings['coach_income'], 2);
  194. // 处理优惠金额
  195. if ($coachProject->discount_amount > 0) {
  196. $order->project->discount_amount = $coachProject->discount_amount;
  197. $order->project->final_price = $order->project->price - $coachProject->discount_amount;
  198. }
  199. // 处理路费
  200. if ($coachProject->traffic_fee > 0) {
  201. $this->calculateTrafficFee($order, $coachProject, $settings);
  202. }
  203. }
  204. /**
  205. * 计算路费
  206. */
  207. private function calculateTrafficFee($order, $coachProject, array $settings): void
  208. {
  209. $trafficFee = $settings['base_price'];
  210. if ($order->distance > $settings['min_distance']) {
  211. $trafficFee += ($order->distance - $settings['min_distance']) * $settings['per_km_price'];
  212. }
  213. $order->project->traffic_fee = round($trafficFee, 2);
  214. // 双程收费
  215. if ($coachProject->is_round_trip) {
  216. $order->project->traffic_fee *= 2;
  217. }
  218. // 计算最终价格
  219. $order->project->final_price = ($order->project->final_price ?? $order->project->price)
  220. + $order->project->traffic_fee;
  221. }
  222. }