OrderService.php 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. <?php
  2. namespace App\Services\Client;
  3. use App\Models\AgentConfig;
  4. use App\Models\AgentInfo;
  5. use App\Models\CoachConfig;
  6. use App\Models\CoachUser;
  7. use App\Models\MemberUser;
  8. use App\Models\Order;
  9. use App\Models\OrderGrabRecord;
  10. use App\Models\OrderRecord;
  11. use App\Models\Project;
  12. use App\Models\SysConfig;
  13. use App\Models\User;
  14. use App\Models\WalletRefundRecord;
  15. use Exception;
  16. use Illuminate\Support\Facades\Auth;
  17. use Illuminate\Support\Facades\DB;
  18. use Illuminate\Support\Facades\Log;
  19. class OrderService
  20. {
  21. protected AgentService $agentService;
  22. protected ProjectService $projectService;
  23. public function __construct(
  24. AgentService $agentService,
  25. ProjectService $projectService
  26. ) {
  27. $this->agentService = $agentService;
  28. $this->projectService = $projectService;
  29. }
  30. /**
  31. * 订单初始化
  32. */
  33. public function initialize(int $userId, array $data): array
  34. {
  35. try {
  36. // 参数验证
  37. abort_if(empty($data), 400, '订单初始化参数不能为空');
  38. DB::beginTransaction();
  39. try {
  40. $user = MemberUser::find($userId);
  41. abort_if(! $user || $user->state != 'enable', 400, '用户状态异常');
  42. // 查询用户钱包
  43. $wallet = $user->wallet;
  44. // 查询默认地址
  45. $address = $user->address;
  46. $areaCode = $address ? $address->area_code : $data['area_code'];
  47. // 查询技师数据
  48. $coach = $this->validateCoach($data['coach_id']);
  49. // 查询技师排班
  50. // $schedule = CoachSchedule::where('coach_id', $coachId)
  51. // ->where('date', date('Y-m-d'))
  52. // ->first();
  53. // // 查询用户优惠券
  54. // $coupons = Coupon::where('user_id', $userId)
  55. // ->where('state', 'enable')
  56. // ->where('expire_time', '>', now())
  57. // ->get();
  58. // 获取项目详情
  59. $project = $this->projectService->getProjectDetail($data['project_id'], $areaCode);
  60. // 计算订单金额
  61. $amounts = $this->calculateOrderAmount($userId, $address?->id, $data['coach_id'], $data['project_id'], $project?->agent_id);
  62. DB::commit();
  63. return [
  64. 'wallet' => $wallet,
  65. 'coach' => $coach,
  66. 'project' => $project,
  67. 'address' => $address,
  68. // 'schedule' => $schedule,
  69. 'amounts' => $amounts,
  70. // 'coupons' => $coupons
  71. ];
  72. } catch (\Exception $e) {
  73. DB::rollBack();
  74. throw $e;
  75. }
  76. } catch (\Exception $e) {
  77. Log::error('订单初始化失败', [
  78. 'userId' => $userId,
  79. 'data' => $data,
  80. 'error' => $e->getMessage(),
  81. ]);
  82. throw $e;
  83. }
  84. }
  85. /**
  86. * 创建订单
  87. */
  88. public function createOrder(int $userId, array $data): array
  89. {
  90. try {
  91. return DB::transaction(function () use ($userId, $data) {
  92. // 1. 参数校验
  93. $user = MemberUser::where('id', $userId)
  94. ->where('state', 'enable')
  95. ->firstOrFail();
  96. $project = Project::where('id', $data['project_id'])
  97. ->where('state', 'enable')
  98. ->firstOrFail();
  99. // 2. 创建订单
  100. $orderType = isset($data['order_id']) ? 'add_time' : 'normal';
  101. // 关键操作:验证必要参数
  102. abort_if(empty($data['project_id']), 400, '项目ID不能为空');
  103. abort_if(empty($data['service_time']), 400, '服务时间不能为空');
  104. abort_if($orderType == 'normal' && empty($data['coach_id']), 400, '技师ID不能为空');
  105. abort_if($orderType == 'normal' && empty($data['address_id']), 400, '地址ID不能为空');
  106. if ($orderType == 'normal') {
  107. // 普通订单验证技师
  108. $coach = $this->validateCoach($data['coach_id']);
  109. } else {
  110. // 加钟订单验证
  111. $originalOrder = $this->getOriginalOrder($user, $data['order_id']);
  112. // 关键操作:验证原订单技师状态
  113. $coach = $this->validateCoach($originalOrder->coach_id);
  114. // 关键操作:验证原订单状态
  115. abort_if(! in_array($originalOrder->state, ['service_ing', 'service_end']), 400, '原订单状态不允许加钟');
  116. $data = $this->prepareAddTimeData($originalOrder, $data);
  117. }
  118. $address = $user->addresses()
  119. ->where('id', $data['address_id'])
  120. ->firstOrFail();
  121. // 计算订单金额
  122. $amounts = $this->calculateOrderAmount($userId, $address->id, $data['coach_id'], $data['project_id'], $project->agent_id, $data['use_balance'] ?? false);
  123. // 关键操作:验证订单金额
  124. abort_if($amounts['total_amount'] <= 0, 400, '订单金额异常');
  125. // 关键操作:验证余额支付
  126. if ($amounts['payment_type'] == 'balance') {
  127. $wallet = $user->wallet;
  128. abort_if($wallet->available_balance < $amounts['balance_amount'], 400, '可用余额不足');
  129. }
  130. // 创建订单记录
  131. $order = $this->createOrderRecord($userId, $data, $orderType, $address, $amounts);
  132. // 余额支付处理
  133. if ($order->payment_type == 'balance') {
  134. $this->handleBalancePayment($user, $order, $orderType);
  135. }
  136. return [
  137. 'order_id' => $order->id,
  138. 'payment_type' => $order->payment_type,
  139. ];
  140. });
  141. } catch (Exception $e) {
  142. Log::error('创建订单失败:', [
  143. 'message' => $e->getMessage(),
  144. 'user_id' => $userId,
  145. 'data' => $data,
  146. ]);
  147. throw $e;
  148. }
  149. }
  150. // 提取方法:验证技师
  151. public function validateCoach(int $coachId): CoachUser
  152. {
  153. $coach = CoachUser::where('id', $coachId)
  154. ->where('state', 'enable')
  155. ->whereHas('info', fn ($q) => $q->where('state', 'approved'))
  156. ->whereHas('qual', fn ($q) => $q->where('state', 'approved'))
  157. ->whereHas('real', fn ($q) => $q->where('state', 'approved'))
  158. ->firstOrFail();
  159. return $coach;
  160. }
  161. // 提取方法:获取原始订单
  162. private function getOriginalOrder($user, $orderId): Order
  163. {
  164. $originalOrder = $user->orders->where('id', $orderId)
  165. ->whereIn('state', ['service_ing', 'service_end'])
  166. ->firstOrFail();
  167. return $originalOrder;
  168. }
  169. // 提取方法:准备加钟订单数据
  170. private function prepareAddTimeData($originalOrder, $data): array
  171. {
  172. if ($originalOrder->state == 'service_ing') {
  173. $startTime = now();
  174. } else {
  175. $startTime = now();
  176. }
  177. return [
  178. ...$data,
  179. 'order_id' => $data['order_id'],
  180. 'address_id' => $originalOrder->address_id,
  181. 'service_time' => $startTime,
  182. 'coach_id' => $originalOrder->coach_id,
  183. ];
  184. }
  185. // 提取方法:创建订单记录
  186. private function createOrderRecord($userId, $data, $orderType, $address, $amounts): Order
  187. {
  188. $order = new Order;
  189. $order->user_id = $userId;
  190. $order->project_id = $data['project_id'];
  191. $order->coach_id = $data['coach_id'];
  192. $order->type = $orderType;
  193. $order->state = 'wait_pay';
  194. $order->source = 'platform';
  195. $order->total_amount = $amounts->total_amount;
  196. $order->balance_amount = $amounts->balance_amount;
  197. $order->pay_amount = $amounts->pay_amount;
  198. $order->project_amount = $amounts->project_amount;
  199. $order->traffic_amount = $orderType == 'add_time' ? 0 : $amounts->delivery_fee;
  200. $order->payment_type = ($data['use_balance'] && $amounts->pay_amount == 0) ? 'balance' : null;
  201. $order->service_time = $data['service_time'];
  202. $order->address_id = $data['address_id'];
  203. $order->longitude = $address->longitude;
  204. $order->latitude = $address->latitude;
  205. $order->location = $address->location;
  206. $order->address = $address->address;
  207. $order->area_code = $address->area_code;
  208. $order->save();
  209. OrderRecord::create([
  210. 'order_id' => $order->id,
  211. 'object_id' => $userId,
  212. 'object_type' => MemberUser::class,
  213. 'state' => $orderType == 'add_time' ? 'add_time' : 'create',
  214. 'remark' => $orderType == 'add_time' ? '加钟订单' : '创建订单',
  215. ]);
  216. return $order;
  217. }
  218. // 提取方法:处理余额支付
  219. private function handleBalancePayment($user, $order, $orderType): void
  220. {
  221. $order->state = $orderType == 'normal' ? 'wait_receive' : 'service_ing';
  222. $order->save();
  223. OrderRecord::create([
  224. 'order_id' => $order->id,
  225. 'object_id' => $user->id,
  226. 'object_type' => MemberUser::class,
  227. 'state' => 'pay',
  228. 'remark' => '余额支付',
  229. ]);
  230. $user->wallet->decrement('total_balance', $order->balance_amount);
  231. $user->wallet->decrement('available_balance', $order->balance_amount);
  232. $user->wallet->save();
  233. }
  234. /**
  235. * 取消订单
  236. */
  237. public function cancelOrder(int $userId, int $orderId): array
  238. {
  239. return DB::transaction(function () use ($userId, $orderId) {
  240. try {
  241. // 1. 验证用户和订单
  242. $order = $this->validateOrderForCancel($userId, $orderId);
  243. // 2. 处理退款
  244. $this->handleCancelRefund($order);
  245. // 3. 完成订单取消
  246. $this->completeCancel($order, $userId);
  247. return ['message' => '订单已取消'];
  248. } catch (Exception $e) {
  249. $this->logCancelOrderError($e, $userId, $orderId);
  250. throw $e;
  251. }
  252. });
  253. }
  254. /**
  255. * 验证订单取消条件
  256. */
  257. private function validateOrderForCancel(int $userId, int $orderId): Order
  258. {
  259. // 复用之前的用户验证逻辑
  260. $user = MemberUser::where('id', $userId)
  261. ->where('state', 'enable')
  262. ->firstOrFail();
  263. // 验证订单状态
  264. $order = Order::where('user_id', $userId)
  265. ->where('id', $orderId)
  266. ->whereIn('state', ['wait_pay', 'wait_receive', 'on_the_way'])
  267. ->lockForUpdate()
  268. ->firstOrFail();
  269. return $order;
  270. }
  271. /**
  272. * 处理订单取消退款
  273. */
  274. private function handleCancelRefund(Order $order): void
  275. {
  276. $user = $order->user;
  277. switch ($order->state) {
  278. case 'wait_receive': // 已接单
  279. // 扣除20%费用
  280. $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.2;
  281. $this->handleRefund($user, $order, $deductAmount, false);
  282. break;
  283. case 'on_the_way': // 已出发
  284. // 扣除50%费用并扣除路费
  285. $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.5;
  286. $this->handleRefund($user, $order, $deductAmount, true);
  287. break;
  288. case 'wait_pay':
  289. // 待支付状态直接取消,无需退款
  290. break;
  291. default:
  292. abort(400, '当前订单状态不允许取消');
  293. }
  294. }
  295. /**
  296. * 完成订单取消
  297. */
  298. private function completeCancel(Order $order, int $userId): void
  299. {
  300. // 添加订单取消记录
  301. OrderRecord::create([
  302. 'order_id' => $order->id,
  303. 'object_id' => $userId,
  304. 'object_type' => MemberUser::class,
  305. 'state' => 'cancel',
  306. 'remark' => '用户取消订单',
  307. ]);
  308. // 修改订单状态
  309. $order->state = 'cancel';
  310. $order->cancel_time = now(); // 添加取消时间
  311. $order->save();
  312. // 如果有技师,可能需要通知技师订单已取消
  313. if ($order->coach_id) {
  314. // TODO: 发送通知给技师
  315. // event(new OrderCancelledEvent($order));
  316. }
  317. }
  318. /**
  319. * 处理退款
  320. */
  321. private function handleRefund(MemberUser $user, Order $order, float $deductAmount, bool $deductTrafficFee): void
  322. {
  323. // 关键操作:计算实际退款金额
  324. $refundAmount = $order->payment_amount + $order->balance_amount;
  325. if ($deductTrafficFee) {
  326. $refundAmount -= $order->traffic_amount;
  327. // TODO: 记录技师路费收入
  328. }
  329. $refundAmount -= $deductAmount;
  330. // 优先从余额支付金额中扣除
  331. $balanceRefund = min($order->balance_amount, $refundAmount);
  332. if ($balanceRefund > 0) {
  333. $this->createRefundRecords($user, $order, $balanceRefund);
  334. }
  335. // 剩余退款金额从支付金额中退还
  336. $paymentRefund = $refundAmount - $balanceRefund;
  337. if ($paymentRefund > 0) {
  338. $this->createRefundRecords($user, $order, $paymentRefund, 'payment');
  339. }
  340. // 记录平台收入
  341. if ($deductAmount > 0) {
  342. // TODO: 添加平台收入记录
  343. // PlatformIncome::create([...]);
  344. }
  345. }
  346. /**
  347. * 创建退款记录
  348. */
  349. private function createRefundRecords($user, $order, $amount, $type = 'balance'): void
  350. {
  351. $refundMethod = $type;
  352. $remark = $type == 'balance' ? '订单取消退还余额' : '订单取消退还支付金额';
  353. // 创建退款记录
  354. $refundRecord = $user->wallet->refundRecords()->create([
  355. 'refund_method' => $refundMethod,
  356. 'total_refund_amount' => $order->payment_amount + $order->balance_amount,
  357. 'actual_refund_amount' => '0.00',
  358. 'wallet_balance_refund_amount' => $type == 'balance' ? $amount : '0.00',
  359. 'recharge_balance_refund_amount' => '0.00',
  360. 'remark' => $remark,
  361. 'order_id' => $order->id,
  362. ]);
  363. // 创建交易记录
  364. $user->wallet->transRecords()->create([
  365. 'amount' => $amount,
  366. 'owner_type' => get_class($refundRecord),
  367. 'owner_id' => $refundRecord->id,
  368. 'remark' => $remark,
  369. 'trans_type' => 'income',
  370. 'storage_type' => 'balance',
  371. 'before_balance' => $user->wallet->total_balance,
  372. 'after_balance' => $user->wallet->total_balance + $amount,
  373. 'before_recharge_balance' => '0.00',
  374. 'after_recharge_balance' => '0.00',
  375. 'trans_time' => now(),
  376. 'state' => 'success',
  377. ]);
  378. // 更新钱包余额
  379. $user->wallet->increment('total_balance', $amount);
  380. $user->wallet->increment('available_balance', $amount);
  381. $user->wallet->save();
  382. }
  383. /**
  384. * 记录订单取消错误日志
  385. */
  386. private function logCancelOrderError(Exception $e, int $userId, int $orderId): void
  387. {
  388. // 复用之前的日志记录方法
  389. Log::error('取消订单失败:', [
  390. 'message' => $e->getMessage(),
  391. 'user_id' => $userId,
  392. 'order_id' => $orderId,
  393. 'trace' => $e->getTraceAsString(),
  394. ]);
  395. }
  396. /**
  397. * 结束订单
  398. */
  399. public function finishOrder(int $userId, int $orderId): array
  400. {
  401. return DB::transaction(function () use ($userId, $orderId) {
  402. try {
  403. // 1. 验证用户和订单
  404. $order = $this->validateOrderForFinish($userId, $orderId);
  405. // 2. 验证技师状态
  406. $this->validateCoach($order->coach_id);
  407. // 3. 验证服务时长
  408. $this->validateServiceDuration($order);
  409. // 4. 完成订单
  410. $this->completeOrder($order, $userId);
  411. return ['message' => '订单已完成'];
  412. } catch (Exception $e) {
  413. $this->logFinishOrderError($e, $userId, $orderId);
  414. throw $e;
  415. }
  416. });
  417. }
  418. /**
  419. * 验证订单完成条件
  420. */
  421. private function validateOrderForFinish(int $userId, int $orderId): Order
  422. {
  423. // 验证用户状态
  424. $user = MemberUser::where('id', $userId)
  425. ->where('state', 'enable')
  426. ->firstOrFail();
  427. // 验证订单状态
  428. $order = Order::where('user_id', $userId)
  429. ->where('id', $orderId)
  430. ->where('state', 'service_ing')
  431. ->lockForUpdate()
  432. ->firstOrFail();
  433. return $order;
  434. }
  435. /**
  436. * 验证服务时长
  437. */
  438. private function validateServiceDuration(Order $order): void
  439. {
  440. // 计算服务时长
  441. $serviceStartTime = $order->service_start_time ?? $order->created_at;
  442. $serviceDuration = now()->diffInMinutes($serviceStartTime);
  443. // 获取项目要求的最短服务时长
  444. $minDuration = $order->project->duration ?? 0;
  445. abort_if($serviceDuration < $minDuration, 400, "服务时长不足{$minDuration}分钟");
  446. }
  447. /**
  448. * 完成订单
  449. */
  450. private function completeOrder(Order $order, int $userId): void
  451. {
  452. // 1. 创建订单记录
  453. OrderRecord::create([
  454. 'order_id' => $order->id,
  455. 'object_id' => $userId,
  456. 'object_type' => MemberUser::class,
  457. 'state' => 'finish',
  458. 'remark' => '服务完成',
  459. ]);
  460. // 2. 更新订单状态
  461. $order->state = 'service_end';
  462. $order->finish_time = now();
  463. $order->save();
  464. }
  465. /**
  466. * 记录订单完成错误日志
  467. */
  468. private function logFinishOrderError(Exception $e, int $userId, int $orderId): void
  469. {
  470. Log::error('结束订单失败:', [
  471. 'message' => $e->getMessage(),
  472. 'user_id' => $userId,
  473. 'order_id' => $orderId,
  474. 'trace' => $e->getTraceAsString(),
  475. ]);
  476. }
  477. /**
  478. * 确认技师离开
  479. */
  480. public function confirmLeave(int $userId, int $orderId): array
  481. {
  482. return DB::transaction(function () use ($userId, $orderId) {
  483. try {
  484. // 1. 参数校验
  485. $order = Order::where('user_id', $userId)
  486. ->where('id', $orderId)
  487. ->where('state', 'service_end') // 订单状态必须是服务结束
  488. ->firstOrFail();
  489. if (! $order) {
  490. throw new Exception('订单不能撤离');
  491. }
  492. // 2. 添加订单撤离记录
  493. OrderRecord::create([
  494. 'order_id' => $orderId,
  495. 'object_id' => $userId,
  496. 'object_type' => MemberUser::class,
  497. 'state' => 'leave',
  498. 'remark' => '技师已离开',
  499. ]);
  500. // 3. 修改订单状态为撤离
  501. $order->state = 'leave';
  502. $order->save();
  503. return ['message' => '已确技师离开'];
  504. } catch (Exception $e) {
  505. Log::error('确认技师离开失败:', [
  506. 'message' => $e->getMessage(),
  507. 'user_id' => $userId,
  508. 'order_id' => $orderId,
  509. ]);
  510. throw $e;
  511. }
  512. });
  513. }
  514. /**
  515. * 获取订单列表
  516. */
  517. public function getOrderList(int $user_id): \Illuminate\Contracts\Pagination\LengthAwarePaginator
  518. {
  519. $user = MemberUser::find($user_id);
  520. return $user->orders()
  521. ->with([
  522. 'coach.info:id,nickname,avatar,gender',
  523. ])
  524. ->orderBy('created_at', 'desc')
  525. ->paginate(10);
  526. }
  527. /**
  528. * 获取订单详情
  529. */
  530. public function getOrderDetail(int $userId, int $orderId): Order
  531. {
  532. $user = MemberUser::find($userId);
  533. return $user->orders()->with([
  534. 'coach.info:id,nickname,avatar,gender',
  535. 'records' => function ($query) {
  536. $query->orderBy('created_at', 'asc');
  537. },
  538. ])
  539. ->firstOrFail();
  540. }
  541. /**
  542. * 订单退款
  543. */
  544. public function refundOrder(int $orderId): array
  545. {
  546. // 使用 Auth::user() 获取用户对象
  547. $user = Auth::user();
  548. return DB::transaction(function () use ($orderId, $user) {
  549. // 查询并锁定订单
  550. $order = Order::where('id', $orderId)
  551. ->where('user_id', $user->id)
  552. ->where('state', 'pending')
  553. ->lockForUpdate()
  554. ->firstOrFail();
  555. // 更新订单状态
  556. $order->state = 'refunded';
  557. $order->save();
  558. // 添加订单记录
  559. OrderRecord::create([
  560. 'order_id' => $orderId,
  561. 'object_id' => $user->id,
  562. 'object_type' => 'user',
  563. 'state' => 'refund',
  564. 'remark' => '订单退款',
  565. ]);
  566. // 创建退款记录
  567. WalletRefundRecord::create([
  568. 'order_id' => $orderId,
  569. 'user_id' => $user->id,
  570. 'amount' => $order->total_amount,
  571. 'state' => 'success',
  572. ]);
  573. return ['message' => '退款成功'];
  574. });
  575. }
  576. /**
  577. * 获取代理商配置
  578. */
  579. public function getAgentConfig(int $agentId): array
  580. {
  581. $agent = AgentInfo::where('id', $agentId)
  582. ->where('state', 'enable')
  583. ->firstOrFail();
  584. // $config = AgentConfig::where('agent_id', $agentId)->firstOrFail();
  585. return [
  586. // 'min_distance' => $config->min_distance,
  587. // 'min_fee' => $config->min_fee,
  588. // 'per_km_fee' => $config->per_km_fee
  589. ];
  590. }
  591. /**
  592. * 获取技师配置
  593. */
  594. public function getCoachConfig(int $coachId): array
  595. {
  596. $coach = CoachUser::where('id', $coachId)
  597. ->where('state', 'enable')
  598. ->where('auth_state', 'passed')
  599. ->firstOrFail();
  600. // $config = CoachConfig::where('coach_id', $coachId)->firstOrFail();
  601. return [
  602. // 'delivery_fee_type' => $config->delivery_fee_type,
  603. // 'charge_delivery_fee' => $config->charge_delivery_fee
  604. ];
  605. }
  606. /**
  607. * 计算路费金额
  608. *
  609. * @param int $coachId 技师ID
  610. * @param int $projectId 项目ID
  611. * @param int|null $agentId 代理商ID
  612. * @param float $distance 距离(公里)
  613. * @return float 路费金额
  614. *
  615. * @throws Exception
  616. */
  617. public function calculateDeliveryFee(
  618. int $coachId,
  619. int $projectId,
  620. ?int $agentId,
  621. float $distance
  622. ): float {
  623. try {
  624. // 1. 校验技师
  625. $coach = CoachUser::where('state', 'enable')
  626. ->whereHas('info', fn ($q) => $q->where('state', 'approved'))
  627. ->whereHas('real', fn ($q) => $q->where('state', 'approved'))
  628. ->whereHas('qual', fn ($q) => $q->where('state', 'approved'))
  629. ->with(['projects' => fn ($q) => $q->where('project_id', $projectId)])
  630. ->find($coachId);
  631. abort_if(! $coach, 404, '技师不存在或状态异常');
  632. // 2. 校验技师项目
  633. $coachProject = $coach->projects->first();
  634. abort_if(! $coachProject, 404, '技师项目不存在');
  635. // 3. 判断是否免收路费
  636. if ($coachProject->traffic_fee_type == 'free') {
  637. return 0;
  638. }
  639. // 4. 获取路费配置
  640. $config = $this->getDeliveryFeeConfig($agentId);
  641. abort_if(! $config, 404, '路费配置不存在');
  642. // 5. 计算路费
  643. $fee = $this->calculateFee($distance, $config);
  644. // 6. 判断是否往返
  645. return $coachProject->delivery_fee_type == 'round_trip'
  646. ? bcmul($fee, '2', 2)
  647. : $fee;
  648. } catch (Exception $e) {
  649. Log::error(__CLASS__.'->'.__FUNCTION__.'计算路费失败:', [
  650. 'message' => $e->getMessage(),
  651. 'coach_id' => $coachId,
  652. 'project_id' => $projectId,
  653. 'agent_id' => $agentId,
  654. 'distance' => $distance,
  655. 'trace' => $e->getTraceAsString(),
  656. ]);
  657. throw $e;
  658. }
  659. }
  660. /**
  661. * 获取路费配置
  662. */
  663. private function getDeliveryFeeConfig(?int $agentId): ?object
  664. {
  665. // 优先获取代理商配置
  666. if ($agentId) {
  667. $agent = AgentInfo::where('state', 'enable')
  668. ->with(['projectConfig'])
  669. ->find($agentId);
  670. if ($agent && $agent->projectConfig) {
  671. return $agent->projectConfig;
  672. }
  673. }
  674. // 获取系统配置
  675. return SysConfig::where('key', 'delivery_fee')->first();
  676. }
  677. /**
  678. * 计算路费
  679. */
  680. private function calculateFee(float $distance, object $config): float
  681. {
  682. // 最小距离内按起步价计算
  683. if ($distance <= $config->min_distance) {
  684. return (float) $config->min_fee;
  685. }
  686. // 超出最小距离部分按每公里费用计算
  687. $extraDistance = bcsub($distance, $config->min_distance, 2);
  688. $extraFee = bcmul($extraDistance, $config->per_km_fee, 2);
  689. return bcadd($config->min_fee, $extraFee, 2);
  690. }
  691. /**
  692. * 计算订单金额
  693. *
  694. * @param int $userId 用户ID
  695. * @param int $addressId 地址ID
  696. * @param int $coachId 技师ID
  697. * @param int $projectId 项目ID
  698. * @param int $agentId 代理商ID
  699. * @param bool $useBalance 是否使用余额
  700. * @param float $distance 距离
  701. *
  702. * @throws Exception
  703. */
  704. public function calculateOrderAmount(
  705. int $userId,
  706. int $addressId,
  707. int $coachId,
  708. int $projectId,
  709. ?int $agentId = null,
  710. bool $useBalance = false,
  711. float $distance = 0
  712. ): array {
  713. try {
  714. // 1. 参数校验
  715. $user = MemberUser::find($userId);
  716. abort_if(! $user || $user->state != 'enable', 404, '用户不存在或状态异常');
  717. // 2. 查询技师项目
  718. $coach = $this->validateCoach($coachId);
  719. abort_if(! $coach, 404, '技师不存在或状态异常');
  720. $coachProject = $coach->projects()
  721. ->where('state', 'enable')
  722. ->where('project_id', $projectId)
  723. ->first();
  724. abort_if(! $coachProject, 404, '技师项目不存在');
  725. // 3. 查询基础项目
  726. $project = Project::where('id', $projectId)
  727. ->where('state', 'enable')
  728. ->first();
  729. abort_if(! $project, 404, '项目不存在或状态异常');
  730. // 4. 计算距离
  731. if ($distance <= 0) {
  732. $address = $user->addresses()->findOrFail($addressId);
  733. $coachService = app(CoachService::class);
  734. $coachDetail = $coachService->getCoachDetail($coachId, $address->latitude, $address->longitude);
  735. $distance = $coachDetail['distance'] ?? 0;
  736. }
  737. // 5. 获取项目价格
  738. $projectAmount = $this->getProjectPrice($project, $agentId, $projectId);
  739. // 6. 计算路费
  740. $deliveryFee = $this->calculateDeliveryFee($coachId, $projectId, $agentId, $distance);
  741. // 7. 计算优惠券金额
  742. $couponAmount = $this->calculateCouponAmount();
  743. // 8. 计算总金额
  744. $totalAmount = bcadd($projectAmount, $deliveryFee, 2);
  745. $totalAmount = bcsub($totalAmount, $couponAmount, 2);
  746. $totalAmount = max(0, $totalAmount);
  747. // 9. 计算余额支付金额
  748. $balanceAmount = 0;
  749. $payAmount = $totalAmount;
  750. if ($useBalance && $totalAmount > 0) {
  751. $wallet = $user->wallet;
  752. abort_if(! $wallet, 404, '用户钱包不存在');
  753. if ($wallet->available_balance >= $totalAmount) {
  754. $balanceAmount = $totalAmount;
  755. $payAmount = 0;
  756. } else {
  757. $balanceAmount = $wallet->available_balance;
  758. $payAmount = bcsub($totalAmount, $balanceAmount, 2);
  759. }
  760. }
  761. return [
  762. 'total_amount' => $totalAmount,
  763. 'balance_amount' => $balanceAmount,
  764. 'pay_amount' => $payAmount,
  765. 'coupon_amount' => $couponAmount,
  766. 'project_amount' => $projectAmount,
  767. 'delivery_fee' => $deliveryFee,
  768. ];
  769. } catch (Exception $e) {
  770. Log::error(__CLASS__.'->'.__FUNCTION__.'计算订单金额失败:', [
  771. 'message' => $e->getMessage(),
  772. 'user_id' => $userId,
  773. 'project_id' => $projectId,
  774. 'trace' => $e->getTraceAsString(),
  775. ]);
  776. throw $e;
  777. }
  778. }
  779. /**
  780. * 获取项目价格
  781. */
  782. private function getProjectPrice($project, ?int $agentId, int $projectId): float
  783. {
  784. $price = $project->price;
  785. if ($agentId) {
  786. $agent = AgentInfo::where('state', 'enable')->find($agentId);
  787. if ($agent) {
  788. $agentProject = $agent->projects()
  789. ->where('state', 'enable')
  790. ->where('project_id', $projectId)
  791. ->first();
  792. if ($agentProject) {
  793. $price = $agentProject->price;
  794. }
  795. }
  796. }
  797. return (float) $price;
  798. }
  799. /**
  800. * 计算优惠券金额
  801. */
  802. private function calculateCouponAmount(): float
  803. {
  804. $couponAmount = 0;
  805. if (request()->has('coupon_id')) {
  806. // TODO: 优惠券逻辑
  807. }
  808. return $couponAmount;
  809. }
  810. /**
  811. * 计算支付金额分配
  812. */
  813. private function calculatePaymentAmounts($user, float $totalAmount, bool $useBalance): array
  814. {
  815. $balanceAmount = 0;
  816. $payAmount = $totalAmount;
  817. if ($useBalance) {
  818. $wallet = $user->wallet;
  819. if (! $wallet) {
  820. throw new Exception('用户钱包不存在');
  821. }
  822. if ($wallet->available_balance >= $totalAmount) {
  823. $balanceAmount = $totalAmount;
  824. $payAmount = 0;
  825. } else {
  826. $balanceAmount = $wallet->available_balance;
  827. $payAmount = bcsub($totalAmount, $balanceAmount, 2);
  828. }
  829. }
  830. return [$balanceAmount, $payAmount];
  831. }
  832. /**
  833. * 指定技师
  834. */
  835. public function assignCoach(int $userId, int $orderId, int $coachId): bool
  836. {
  837. return DB::transaction(function () use ($userId, $orderId, $coachId) {
  838. try {
  839. // 参数校验
  840. $user = MemberUser::where('id', $userId)
  841. ->where('state', 'enable')
  842. ->firstOrFail();
  843. $order = Order::where('id', $orderId)
  844. ->where('user_id', $userId)
  845. ->whereIn('state', [0, 1, 6])
  846. ->firstOrFail();
  847. $coach = $this->validateCoach($coachId);
  848. // 修改订单
  849. $order->coach_id = $coachId;
  850. if ($order->state == 'created') {
  851. $amounts = $this->calculateOrderAmount(
  852. $userId,
  853. $order->address_id,
  854. $coachId,
  855. $order->project_id,
  856. $order->agent_id,
  857. $order->payment_type === 'balance'
  858. );
  859. $order->total_amount = $amounts->total_amount;
  860. $order->balance_amount = $amounts->balance_amount;
  861. $order->pay_amount = $amounts->pay_amount;
  862. $order->coupon_amount = $amounts->coupon_amount;
  863. $order->project_amount = $amounts->project_amount;
  864. $order->delivery_fee = $amounts->delivery_fee;
  865. if ($order->payment_type === 'balance') {
  866. $order->state = 'paid';
  867. }
  868. }
  869. if ($order->state == 'paid') {
  870. $order->state = 'assigned';
  871. }
  872. $order->save();
  873. // 创建订单历史
  874. OrderRecord::create([
  875. 'order_id' => $order->id,
  876. 'type' => 'assigned',
  877. 'user_id' => $userId,
  878. 'coach_id' => $coachId,
  879. ]);
  880. OrderRecord::create([
  881. 'order_id' => $order->id,
  882. 'type' => 'accepted',
  883. 'user_id' => $userId,
  884. 'coach_id' => $coachId,
  885. 'remark' => '抢单成功',
  886. ]);
  887. // 更新抢单池
  888. OrderGrabRecord::where('order_id', $orderId)
  889. ->update(['state' => 'success', 'coach_id' => $coachId]);
  890. return true;
  891. } catch (Exception $e) {
  892. Log::error('分配技师失败:', [
  893. 'message' => $e->getMessage(),
  894. 'user_id' => $userId,
  895. 'order_id' => $orderId,
  896. 'coach_id' => $coachId,
  897. ]);
  898. throw $e;
  899. }
  900. });
  901. }
  902. }