OrderService.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359
  1. <?php
  2. namespace App\Services\Client;
  3. use App\Enums\OrderGrabRecordStatus;
  4. use App\Enums\OrderRecordStatus;
  5. use App\Enums\OrderSource;
  6. use App\Enums\OrderStatus;
  7. use App\Enums\OrderType;
  8. use App\Enums\PaymentMethod;
  9. use App\Enums\ProjectStatus;
  10. use App\Enums\TechnicianAuthStatus;
  11. use App\Enums\TechnicianStatus;
  12. use App\Enums\UserStatus;
  13. use App\Models\AgentConfig;
  14. use App\Models\AgentInfo;
  15. use App\Models\CoachConfig;
  16. use App\Models\CoachUser;
  17. use App\Models\MemberUser;
  18. use App\Models\Order;
  19. use App\Models\OrderGrabRecord;
  20. use App\Models\OrderRecord;
  21. use App\Models\Project;
  22. use App\Models\SysConfig;
  23. use App\Models\User;
  24. use App\Models\WalletPaymentRecord;
  25. use App\Models\WalletRefundRecord;
  26. use Exception;
  27. use Illuminate\Support\Facades\Auth;
  28. use Illuminate\Support\Facades\DB;
  29. use Illuminate\Support\Facades\Log;
  30. use Illuminate\Support\Facades\Redis;
  31. use SimpleSoftwareIO\QrCode\Facades\QrCode;
  32. class OrderService
  33. {
  34. protected AgentService $agentService;
  35. protected ProjectService $projectService;
  36. public function __construct(
  37. AgentService $agentService,
  38. ProjectService $projectService
  39. ) {
  40. $this->agentService = $agentService;
  41. $this->projectService = $projectService;
  42. }
  43. /**
  44. * 订单初始化
  45. *
  46. * 初始化订单信息,包括用户钱包、技师信息、项目信息、地址信息和订单金额等
  47. *
  48. * @param int $userId 用户ID
  49. * @param array $data 订单数据
  50. * @return array 返回初始化的订单信息
  51. *
  52. * @throws \Exception 初始化失败时抛出异常
  53. */
  54. public function initialize(int $userId, array $data): array
  55. {
  56. try {
  57. // 参数验证
  58. abort_if(empty($data['project_id']), 400, '项目ID不能为空');
  59. abort_if(empty($data['coach_id']), 400, '技师ID不能为空');
  60. return DB::transaction(function () use ($userId, $data) {
  61. $user = MemberUser::find($userId);
  62. abort_if(! $user || $user->state != 'enable', 400, '用户状态异常');
  63. // 查询用户钱包
  64. $wallet = $user->wallet;
  65. // 查询默认地址
  66. $address = $user->address;
  67. $areaCode = $address ? $address->area_code : ($data['area_code'] ?? null);
  68. abort_if(empty($areaCode), 400, '区域编码不能为空');
  69. // 查询技师数据
  70. $coach = $this->validateCoach($data['coach_id']);
  71. // 获取项目详情
  72. $project = $this->projectService->getProjectDetail($data['project_id'], $areaCode);
  73. abort_if(! $project, 400, '项目不存在');
  74. // 计算订单金额
  75. $amounts = $this->calculateOrderAmount(
  76. $userId,
  77. $address?->id ?? 0,
  78. $data['coach_id'],
  79. $data['project_id'],
  80. $project->agent_id,
  81. false,
  82. $data['latitude'],
  83. $data['longitude']
  84. );
  85. return [
  86. 'wallet' => $wallet,
  87. 'coach' => $coach,
  88. 'project' => $project,
  89. 'address' => $address,
  90. 'amounts' => $amounts,
  91. ];
  92. });
  93. } catch (Exception $e) {
  94. Log::error('订单初始化失败', [
  95. 'userId' => $userId,
  96. 'data' => $data,
  97. 'error' => $e->getMessage(),
  98. 'trace' => $e->getTraceAsString(),
  99. ]);
  100. throw $e;
  101. }
  102. }
  103. /**
  104. * 创建订单
  105. */
  106. public function createOrder(int $userId, array $data): array
  107. {
  108. return DB::transaction(function () use ($userId, $data) {
  109. // 1. 参数校验
  110. $user = MemberUser::where('id', $userId)
  111. ->where('state', UserStatus::OPEN->value())
  112. ->firstOrFail();
  113. $project = Project::where('id', $data['project_id'])
  114. ->where('state', ProjectStatus::OPEN->value())
  115. ->firstOrFail();
  116. // 2. 订单类型判断
  117. $orderType = $data['order_type'];
  118. // 关键操作:验证必要参数
  119. abort_if(empty($data['project_id']), 400, '项目ID不能为空');
  120. // 上门订单必须指定技师和地址
  121. abort_if($orderType == OrderType::VISIT->value && empty($data['coach_id']), 400, '技师ID不能为空');
  122. abort_if($orderType == OrderType::VISIT->value && empty($data['address_id']), 400, '地址ID不能为空');
  123. // 抢单订单必须指定地址
  124. abort_if($orderType == OrderType::GRAB->value && empty($data['address_id']), 400, '地址ID不能为空');
  125. // 加钟订单必须指定原订单
  126. abort_if($orderType == OrderType::OVERTIME->value && empty($data['order_id']), 400, '原订单ID不能为空');
  127. // 到店订单必须指定店铺
  128. abort_if($orderType == OrderType::SHOP->value && empty($data['shop_id']), 400, '店铺ID不能为空');
  129. // 应急订单必须指定地址
  130. abort_if($orderType == OrderType::EMERGENCY->value && empty($data['address_id']), 400, '地址ID不能为空');
  131. // 3. 验证技师
  132. // 4. 根据订单类型处理
  133. // 上门订单
  134. if ($orderType == OrderType::VISIT->value) {
  135. $this->validateCoach($data['coach_id']);
  136. }
  137. // 加钟订单
  138. if ($orderType == OrderType::OVERTIME->value) {
  139. $originalOrder = $this->getOriginalOrder($user, $data['order_id']);
  140. $data['address_id'] = $originalOrder->address_id;
  141. $this->validateCoach($originalOrder->coach_id);
  142. abort_if(! in_array($originalOrder->state, ['service_ing', 'service_end']), 400, '原订单状态不允许加钟');
  143. $data = $this->prepareAddTimeData($originalOrder, $data);
  144. }
  145. $address = $user->addresses()
  146. ->where('id', $data['address_id'])
  147. ->firstOrFail();
  148. // 5. 计算订单金额
  149. $data['use_balance'] = $data['use_balance'] ?? false;
  150. $amounts = $this->calculateOrderAmount(
  151. $userId,
  152. $address->id,
  153. $data['coach_id'],
  154. $data['project_id'],
  155. $project?->agent_id,
  156. $data['use_balance']
  157. );
  158. // 6. 验证金额和余额
  159. abort_if($amounts['total_amount'] <= 0, 400, '订单金额异常');
  160. if ($data['payment_type'] == PaymentMethod::BALANCE->value) {
  161. $wallet = $user->wallet;
  162. abort_if($wallet->available_balance < $amounts['balance_amount'], 400, '可用余额不足');
  163. }
  164. // 7. 创建订单记录
  165. $order = $this->createOrderRecord($userId, $data, $orderType, $address, $data['payment_type'], (object) $amounts);
  166. // 8. 余额支付处理
  167. if ($order->payment_type == PaymentMethod::BALANCE->value && $orderType != OrderType::GRAB->value) {
  168. $this->handleBalancePayment($user, $order, $orderType);
  169. }
  170. return [
  171. 'order_id' => $order->id,
  172. 'payment_type' => $order->payment_type,
  173. ];
  174. });
  175. }
  176. // 提取方法:验证技师
  177. public function validateCoach(int $coachId): CoachUser
  178. {
  179. return CoachUser::where('id', $coachId)
  180. ->where('state', TechnicianStatus::ACTIVE->value)
  181. ->whereHas('info', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
  182. ->whereHas('qual', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
  183. ->whereHas('real', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
  184. ->firstOrFail();
  185. }
  186. // 提取方法:获取原始订单
  187. private function getOriginalOrder($user, $orderId): Order
  188. {
  189. $originalOrder = $user->orders->where('id', $orderId)
  190. ->whereIn('state', [OrderStatus::SERVING->value, OrderStatus::FINISHED->value])
  191. ->firstOrFail();
  192. return $originalOrder;
  193. }
  194. // 提取方法:准备加钟订单数据
  195. private function prepareAddTimeData($originalOrder, $data): array
  196. {
  197. if ($originalOrder->state == OrderStatus::SERVING->value) {
  198. $startTime = now();
  199. } else {
  200. $startTime = now();
  201. }
  202. return [
  203. ...$data,
  204. 'order_id' => $data['order_id'],
  205. 'address_id' => $originalOrder->address_id,
  206. 'service_time' => $startTime,
  207. 'coach_id' => $originalOrder->coach_id,
  208. ];
  209. }
  210. // 提取方法:创建订单记录
  211. private function createOrderRecord($userId, $data, $orderType, $address, $payment_type, object $amounts): Order
  212. {
  213. $order = new Order;
  214. $order->user_id = $userId;
  215. $order->project_id = $data['project_id'];
  216. $order->coach_id = $data['coach_id'];
  217. $order->type = $orderType;
  218. $order->state = OrderStatus::CREATED->value;
  219. $order->source = OrderSource::PLATFORM->value;
  220. $order->total_amount = $amounts->total_amount;
  221. $order->balance_amount = $amounts->balance_amount;
  222. $order->pay_amount = $amounts->pay_amount;
  223. $order->project_amount = $amounts->project_amount;
  224. $order->traffic_amount = $orderType == OrderType::OVERTIME->value ? 0 : $amounts->delivery_fee;
  225. $order->payment_type = ($data['use_balance'] && $amounts->pay_amount == 0) ? PaymentMethod::BALANCE->value : $payment_type;
  226. $order->service_time = $data['service_time'];
  227. $order->address_id = $data['address_id'];
  228. $order->longitude = $address->longitude;
  229. $order->latitude = $address->latitude;
  230. $order->location = $address->location;
  231. $order->address = $address->address;
  232. $order->area_code = $address->area_code;
  233. $order->save();
  234. // 创建订单记录
  235. OrderRecord::create([
  236. 'order_id' => $order->id,
  237. 'object_id' => $userId,
  238. 'object_type' => MemberUser::class,
  239. 'state' => OrderStatus::CREATED->value,
  240. 'remark' => $orderType == OrderType::OVERTIME->value ? '加钟订单' : '创建订单',
  241. ]);
  242. if ($orderType == OrderType::GRAB->value) {
  243. // 将订单地址经纬度存入redis的geo类型
  244. Redis::geoadd(
  245. 'order_locations',
  246. $order->longitude,
  247. $order->latitude,
  248. 'order:'.$order->id
  249. );
  250. }
  251. return $order;
  252. }
  253. // 提取方法:处理余额支付
  254. private function handleBalancePayment($user, $order, $orderType): void
  255. {
  256. $order->state = $orderType == OrderType::VISIT->value || $orderType == OrderType::GRAB->value ? OrderStatus::PAID->value : OrderStatus::SERVING->value;
  257. $order->save();
  258. OrderRecord::create([
  259. 'order_id' => $order->id,
  260. 'object_id' => $user->id,
  261. 'object_type' => MemberUser::class,
  262. 'state' => OrderRecordStatus::PAID->value,
  263. 'remark' => '余额支付',
  264. ]);
  265. $user->wallet->decrement('total_balance', $order->balance_amount);
  266. $user->wallet->decrement('available_balance', $order->balance_amount);
  267. }
  268. /**
  269. * 取消订单
  270. */
  271. public function cancelOrder(int $userId, int $orderId): array
  272. {
  273. return DB::transaction(function () use ($userId, $orderId) {
  274. try {
  275. // 1. 验证用户和订单
  276. $order = $this->validateOrderForCancel($userId, $orderId);
  277. abort_if($order->state == 'cancel', 400, '订单已取消');
  278. // 2. 处理退款
  279. if (in_array($order->state, ['wait_receive', 'on_the_way'])) {
  280. $this->handleCancelRefund($order);
  281. }
  282. // 3. 完成订单取消
  283. $this->completeCancel($order, $userId);
  284. // 4. 通知技师
  285. if ($order->coach_id) {
  286. // event(new OrderCancelledEvent($order));
  287. }
  288. return ['message' => '订单已取消'];
  289. } catch (Exception $e) {
  290. $this->logCancelOrderError($e, $userId, $orderId);
  291. throw $e;
  292. }
  293. });
  294. }
  295. /**
  296. * 验证订单取消条件
  297. */
  298. private function validateOrderForCancel(int $userId, int $orderId): Order
  299. {
  300. // 复用之前的用户验证逻辑
  301. $user = MemberUser::where('id', $userId)
  302. ->where('state', UserStatus::OPEN->value)
  303. ->firstOrFail();
  304. // 验证订单状态
  305. $order = Order::where('user_id', $userId)
  306. ->where('id', $orderId)
  307. ->whereIn('state', [OrderStatus::CREATED->value, OrderStatus::ASSIGNED->value, OrderStatus::PAID->value, OrderStatus::ACCEPTED->value, OrderStatus::DEPARTED->value])
  308. ->lockForUpdate()
  309. ->firstOrFail();
  310. return $order;
  311. }
  312. /**
  313. * 处理订单取消退款
  314. */
  315. private function handleCancelRefund(Order $order): void
  316. {
  317. $user = $order->user;
  318. switch ($order->state) {
  319. case OrderStatus::ACCEPTED->value: // 已接单
  320. // 扣除20%费用
  321. $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.2;
  322. $this->handleRefund($user, $order, $deductAmount, false);
  323. break;
  324. case OrderStatus::DEPARTED->value: // 已出发
  325. // 扣除50%费用并扣除路费
  326. $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.5;
  327. $this->handleRefund($user, $order, $deductAmount, true);
  328. break;
  329. case OrderStatus::CREATED->value:
  330. // 待支付状态直接取消,无需退款
  331. break;
  332. default:
  333. abort(400, '当前订单状态不允许取消');
  334. }
  335. }
  336. /**
  337. * 完成订单取消
  338. */
  339. private function completeCancel(Order $order, int $userId): void
  340. {
  341. // 添加订单取消记录
  342. OrderRecord::create([
  343. 'order_id' => $order->id,
  344. 'object_id' => $userId,
  345. 'object_type' => MemberUser::class,
  346. 'state' => 'cancel',
  347. 'remark' => '用户取消订单',
  348. ]);
  349. // 修改订单状态
  350. $order->state = 'cancel';
  351. $order->cancel_time = now(); // 添加取消时间
  352. $order->save();
  353. // 如果有技师,可能需要通知技师订单已取消
  354. if ($order->coach_id) {
  355. // TODO: 发送通知给技师
  356. // event(new OrderCancelledEvent($order));
  357. }
  358. }
  359. /**
  360. * 处理退款
  361. */
  362. private function handleRefund(MemberUser $user, Order $order, float $deductAmount, bool $deductTrafficFee): void
  363. {
  364. // 关键操作:计算实际退款金额
  365. $refundAmount = $order->payment_amount + $order->balance_amount;
  366. if ($deductTrafficFee) {
  367. $refundAmount -= $order->traffic_amount;
  368. // TODO: 记录技师路费收入
  369. }
  370. $refundAmount -= $deductAmount;
  371. // 优先余额支付金额中扣除
  372. $balanceRefund = min($order->balance_amount, $refundAmount);
  373. if ($balanceRefund > 0) {
  374. $this->createRefundRecords($user, $order, $balanceRefund);
  375. }
  376. // 剩余退款金额从支付金额中退还
  377. $paymentRefund = $refundAmount - $balanceRefund;
  378. if ($paymentRefund > 0) {
  379. $this->createRefundRecords($user, $order, $paymentRefund, 'payment');
  380. }
  381. // 记录平台收入
  382. if ($deductAmount > 0) {
  383. // TODO: 添加平台收入记录
  384. // PlatformIncome::create([...]);
  385. }
  386. }
  387. /**
  388. * 创建退款记录
  389. */
  390. private function createRefundRecords($user, $order, $amount, $type = 'balance'): void
  391. {
  392. $refundMethod = $type;
  393. $remark = $type == 'balance' ? '订单取消退还余额' : '订单取消退还支付金额';
  394. // 创建退款记录
  395. $refundRecord = $user->wallet->refundRecords()->create([
  396. 'refund_method' => $refundMethod,
  397. 'total_refund_amount' => $order->payment_amount + $order->balance_amount,
  398. 'actual_refund_amount' => '0.00',
  399. 'wallet_balance_refund_amount' => $type == 'balance' ? $amount : '0.00',
  400. 'recharge_balance_refund_amount' => '0.00',
  401. 'remark' => $remark,
  402. 'order_id' => $order->id,
  403. ]);
  404. // 创建交易记录
  405. $user->wallet->transRecords()->create([
  406. 'amount' => $amount,
  407. 'owner_type' => get_class($refundRecord),
  408. 'owner_id' => $refundRecord->id,
  409. 'remark' => $remark,
  410. 'trans_type' => 'income',
  411. 'storage_type' => 'balance',
  412. 'before_balance' => $user->wallet->total_balance,
  413. 'after_balance' => $user->wallet->total_balance + $amount,
  414. 'before_recharge_balance' => '0.00',
  415. 'after_recharge_balance' => '0.00',
  416. 'trans_time' => now(),
  417. 'state' => 'success',
  418. ]);
  419. // 更新钱包余额
  420. $user->wallet->increment('total_balance', $amount);
  421. $user->wallet->increment('available_balance', $amount);
  422. $user->wallet->save();
  423. }
  424. /**
  425. * 记录订单取消错误日志
  426. */
  427. private function logCancelOrderError(Exception $e, int $userId, int $orderId): void
  428. {
  429. // 复用之前的日记录方法
  430. Log::error('取消订单失败:', [
  431. 'message' => $e->getMessage(),
  432. 'user_id' => $userId,
  433. 'order_id' => $orderId,
  434. 'trace' => $e->getTraceAsString(),
  435. ]);
  436. }
  437. /**
  438. * 结束订单
  439. */
  440. public function finishOrder(int $userId, int $orderId): array
  441. {
  442. return DB::transaction(function () use ($userId, $orderId) {
  443. try {
  444. // 1. 验证用户和订单
  445. $order = $this->validateOrderForFinish($userId, $orderId);
  446. abort_if($order->state == OrderStatus::FINISHED->value, 400, '订单已完成');
  447. // 2. 验证技师状态
  448. $coach = $this->validateCoach($order->coach_id);
  449. // 4. 完成订单
  450. $this->completeOrder($order, $userId);
  451. // 5. 通知技师
  452. // event(new OrderFinishedEvent($order));
  453. return ['message' => '订单已完成'];
  454. } catch (Exception $e) {
  455. $this->logFinishOrderError($e, $userId, $orderId);
  456. throw $e;
  457. }
  458. });
  459. }
  460. /**
  461. * 验证订单完成条件
  462. */
  463. private function validateOrderForFinish(int $userId, int $orderId): Order
  464. {
  465. // 验证用户状态
  466. $user = MemberUser::where('id', $userId)
  467. ->where('state', UserStatus::OPEN->value)
  468. ->firstOrFail();
  469. // 验证订单状态
  470. $order = Order::where('user_id', $userId)
  471. ->where('id', $orderId)
  472. ->where('state', OrderStatus::SERVING->value)
  473. ->lockForUpdate()
  474. ->firstOrFail();
  475. return $order;
  476. }
  477. /**
  478. * 完成订单
  479. */
  480. private function completeOrder(Order $order, int $userId): void
  481. {
  482. // 1. 创建订单记录
  483. OrderRecord::create([
  484. 'order_id' => $order->id,
  485. 'object_id' => $userId,
  486. 'object_type' => MemberUser::class,
  487. 'state' => OrderRecordStatus::COMPLETED->value,
  488. 'remark' => '服务完成',
  489. ]);
  490. // 2. 更新订单状态
  491. $order->state = OrderStatus::FINISHED->value;
  492. $order->save();
  493. }
  494. /**
  495. * 记录订单完成错误日志
  496. */
  497. private function logFinishOrderError(Exception $e, int $userId, int $orderId): void
  498. {
  499. Log::error('结束订单失败:', [
  500. 'message' => $e->getMessage(),
  501. 'user_id' => $userId,
  502. 'order_id' => $orderId,
  503. 'trace' => $e->getTraceAsString(),
  504. ]);
  505. }
  506. /**
  507. * 确认技师离开
  508. */
  509. public function confirmLeave(int $userId, int $orderId): array
  510. {
  511. return DB::transaction(function () use ($userId, $orderId) {
  512. try {
  513. // 1. 参数校验
  514. $order = Order::where('user_id', $userId)
  515. ->where('id', $orderId)
  516. ->where('state', OrderStatus::FINISHED->value) // 订单状态必须是服务结束
  517. ->firstOrFail();
  518. if (! $order) {
  519. throw new Exception('订单不能撤离');
  520. }
  521. // 2. 添加订单撤离记录
  522. OrderRecord::create([
  523. 'order_id' => $orderId,
  524. 'object_id' => $userId,
  525. 'object_type' => MemberUser::class,
  526. 'state' => OrderRecordStatus::LEFT->value,
  527. 'remark' => '技师已离开',
  528. ]);
  529. // 3. 修改订单状态为撤离
  530. $order->state = OrderStatus::LEFT->value;
  531. $order->save();
  532. return ['message' => '已确技师离开'];
  533. } catch (Exception $e) {
  534. Log::error('确认技师离开失败:', [
  535. 'message' => $e->getMessage(),
  536. 'user_id' => $userId,
  537. 'order_id' => $orderId,
  538. ]);
  539. throw $e;
  540. }
  541. });
  542. }
  543. /**
  544. * 获取订单列表
  545. */
  546. public function getOrderList(int $user_id): \Illuminate\Contracts\Pagination\LengthAwarePaginator
  547. {
  548. $user = MemberUser::find($user_id);
  549. return $user->orders()
  550. ->with([
  551. 'coach.info:id,nickname,avatar,gender',
  552. ])
  553. ->orderBy('created_at', 'desc')
  554. ->paginate(10);
  555. }
  556. /**
  557. * 获取订单详情
  558. */
  559. public function getOrderDetail(int $userId, int $orderId): Order
  560. {
  561. $user = MemberUser::find($userId);
  562. return $user->orders()
  563. ->where('id', $orderId) // 需要添加订单ID条件
  564. ->with([
  565. 'coach.info:id,nickname,avatar,gender',
  566. 'records' => function ($query) {
  567. $query->orderBy('created_at', 'asc');
  568. },
  569. ])
  570. ->firstOrFail();
  571. }
  572. /**
  573. * 订单退款
  574. */
  575. public function refundOrder(int $orderId): array
  576. {
  577. // 使用 Auth::user() 获取用户对象
  578. $user = Auth::user();
  579. return DB::transaction(function () use ($orderId, $user) {
  580. // 查询并锁定订单
  581. $order = Order::where('id', $orderId)
  582. ->where('user_id', $user->id)
  583. ->where('state', 'pending')
  584. ->lockForUpdate()
  585. ->firstOrFail();
  586. // 更新订单状态
  587. $order->state = 'refunded';
  588. $order->save();
  589. // 添加订单记录
  590. OrderRecord::create([
  591. 'order_id' => $orderId,
  592. 'object_id' => $user->id,
  593. 'object_type' => 'user',
  594. 'state' => 'refund',
  595. 'remark' => '订单退款',
  596. ]);
  597. // 创建退款记录
  598. WalletRefundRecord::create([
  599. 'order_id' => $orderId,
  600. 'user_id' => $user->id,
  601. 'amount' => $order->total_amount,
  602. 'state' => 'success',
  603. ]);
  604. return ['message' => '退款成功'];
  605. });
  606. }
  607. /**
  608. * 获取代理商配置
  609. */
  610. public function getAgentConfig(int $agentId): array
  611. {
  612. $agent = AgentInfo::where('id', $agentId)
  613. ->where('state', 'enable')
  614. ->firstOrFail();
  615. // $config = AgentConfig::where('agent_id', $agentId)->firstOrFail();
  616. return [
  617. // 'min_distance' => $config->min_distance,
  618. // 'min_fee' => $config->min_fee,
  619. // 'per_km_fee' => $config->per_km_fee
  620. ];
  621. }
  622. /**
  623. * 获取技师配置
  624. */
  625. public function getCoachConfig(int $coachId): array
  626. {
  627. $coach = CoachUser::where('id', $coachId)
  628. ->where('state', 'enable')
  629. ->where('auth_state', 'passed')
  630. ->firstOrFail();
  631. // $config = CoachConfig::where('coach_id', $coachId)->firstOrFail();
  632. return [
  633. // 'delivery_fee_type' => $config->delivery_fee_type,
  634. // 'charge_delivery_fee' => $config->charge_delivery_fee
  635. ];
  636. }
  637. /**
  638. * 计算路费金额
  639. *
  640. * @param int $coachId 技师ID
  641. * @param int $projectId 项目ID
  642. * @param int|null $agentId 代理商ID
  643. * @param float $distance 距离(公里)
  644. * @return float 路费金额
  645. *
  646. * @throws Exception
  647. */
  648. public function calculateDeliveryFee(
  649. int $coachId,
  650. int $projectId,
  651. ?int $agentId,
  652. float $distance
  653. ): float {
  654. try {
  655. // 1. 校验技师
  656. $coach = CoachUser::where('state', TechnicianStatus::ACTIVE->value)
  657. ->whereHas('info', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
  658. ->whereHas('real', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
  659. ->whereHas('qual', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
  660. ->with(['projects' => fn ($q) => $q->where('project_id', $projectId)])
  661. ->find($coachId);
  662. abort_if(! $coach, 404, '技师不存在或状态异常');
  663. // 2. 校验技师项目
  664. $coachProject = $coach->projects->first();
  665. abort_if(! $coachProject, 404, '技师项目不存在');
  666. // 3. 判断是否免收路费
  667. if ($coachProject->traffic_fee_type == 'free') {
  668. return 0;
  669. }
  670. // 4. 获取路费配置
  671. $config = $this->getDeliveryFeeConfig($agentId);
  672. abort_if(! $config, 404, '路费配置不存在');
  673. // 5. 计算路费
  674. $fee = $this->calculateFee($distance, $config);
  675. // 6. 判断是否往返
  676. return $coachProject->delivery_fee_type == 'round_trip'
  677. ? bcmul($fee, '2', 2)
  678. : $fee;
  679. } catch (Exception $e) {
  680. Log::error(__CLASS__.'->'.__FUNCTION__.'计算路费失败:', [
  681. 'message' => $e->getMessage(),
  682. 'coach_id' => $coachId,
  683. 'project_id' => $projectId,
  684. 'agent_id' => $agentId,
  685. 'distance' => $distance,
  686. 'trace' => $e->getTraceAsString(),
  687. ]);
  688. throw $e;
  689. }
  690. }
  691. /**
  692. * 获取路费配置
  693. */
  694. private function getDeliveryFeeConfig(?int $agentId): ?object
  695. {
  696. // 优先获取代理商配置
  697. if ($agentId) {
  698. $agent = AgentInfo::where('state', 'enable')
  699. ->with(['projectConfig'])
  700. ->find($agentId);
  701. if ($agent && $agent->projectConfig) {
  702. return $agent->projectConfig;
  703. }
  704. }
  705. // 获取系统配置
  706. return SysConfig::where('key', 'delivery_fee')->first();
  707. }
  708. /**
  709. * 计算路费
  710. */
  711. private function calculateFee(float $distance, object $config): float
  712. {
  713. // 最小距离内按起步价计算
  714. if ($distance <= $config->min_distance) {
  715. return (float) $config->min_fee;
  716. }
  717. // 超出最小距离部分按每公里费用计算
  718. $extraDistance = bcsub($distance, $config->min_distance, 2);
  719. $extraFee = bcmul($extraDistance, $config->per_km_fee, 2);
  720. return bcadd($config->min_fee, $extraFee, 2);
  721. }
  722. /**
  723. * 计算订单金额
  724. *
  725. * @param int $userId 用户ID
  726. * @param int $addressId 地址ID
  727. * @param int $coachId 技师ID
  728. * @param int $projectId 项目ID
  729. * @param int $agentId 代理商ID
  730. * @param bool $useBalance 是否使用余额
  731. * @param float $distance 距离
  732. * @param int $lat 纬度
  733. * @param int $lng 经度
  734. *
  735. * @throws Exception
  736. */
  737. public function calculateOrderAmount(
  738. int $userId,
  739. int $addressId,
  740. int $coachId,
  741. int $projectId,
  742. ?int $agentId = null,
  743. bool $useBalance = false,
  744. float $distance = 0,
  745. int $lat = 0,
  746. int $lng = 0
  747. ): array {
  748. try {
  749. // 1. 参数校验
  750. $user = MemberUser::find($userId);
  751. abort_if(! $user || $user->state != UserStatus::OPEN->value, 404, '用户不存在或状态异常');
  752. // 2. 查询技师项目
  753. $coach = $this->validateCoach($coachId);
  754. abort_if(! $coach, 404, '技师不存在或状态异常');
  755. $coachProject = $coach->projects()
  756. ->where('state', ProjectStatus::OPEN->value)
  757. ->where('project_id', $projectId)
  758. ->first();
  759. abort_if(! $coachProject, 404, '技师项目不存在');
  760. // 3. 查询基础项目
  761. $project = Project::where('id', $projectId)
  762. ->where('state', ProjectStatus::OPEN->value())
  763. ->first();
  764. abort_if(! $project, 404, '项目不存在或状态异常');
  765. // 4. 计算距离
  766. if (floatval($distance) <= 0) {
  767. $address = $user->addresses()->find($addressId) ?? ['latitude' => $lat, 'longitude' => $lng];
  768. $coachService = app(CoachService::class);
  769. $coachDetail = $coachService->getCoachDetail($coachId, $address['latitude'], $address['longitude']);
  770. $distance = $coachDetail['distance'] ?? 0;
  771. }
  772. // 5. 获取项目价格
  773. $projectAmount = $this->getProjectPrice($project, $agentId, $projectId);
  774. // 6. 计算路费
  775. $deliveryFee = $this->calculateDeliveryFee($coachId, $projectId, $agentId, $distance);
  776. // 7. 计算优惠券金额
  777. $couponAmount = $this->calculateCouponAmount();
  778. // 8. 计算总金额
  779. $totalAmount = bcadd($projectAmount, $deliveryFee, 2);
  780. $totalAmount = bcsub($totalAmount, $couponAmount, 2);
  781. $totalAmount = max(0, $totalAmount);
  782. // 9. 计算余额支付金额
  783. $balanceAmount = 0;
  784. $payAmount = $totalAmount;
  785. if ($useBalance && $totalAmount > 0) {
  786. $wallet = $user->wallet;
  787. abort_if(! $wallet, 404, '用户钱包不存在');
  788. if ($wallet->available_balance >= $totalAmount) {
  789. $balanceAmount = $totalAmount;
  790. $payAmount = 0;
  791. } else {
  792. $balanceAmount = $wallet->available_balance;
  793. $payAmount = bcsub($totalAmount, $balanceAmount, 2);
  794. }
  795. }
  796. return [
  797. 'total_amount' => $totalAmount,
  798. 'balance_amount' => $balanceAmount,
  799. 'pay_amount' => $payAmount,
  800. 'coupon_amount' => $couponAmount,
  801. 'project_amount' => $projectAmount,
  802. 'delivery_fee' => $deliveryFee,
  803. ];
  804. } catch (Exception $e) {
  805. Log::error(__CLASS__.'->'.__FUNCTION__.'计算订单金额失败:', [
  806. 'message' => $e->getMessage(),
  807. 'user_id' => $userId,
  808. 'project_id' => $projectId,
  809. 'trace' => $e->getTraceAsString(),
  810. ]);
  811. throw $e;
  812. }
  813. }
  814. /**
  815. * 获取项目价格
  816. */
  817. private function getProjectPrice($project, ?int $agentId, int $projectId): float
  818. {
  819. $price = $project->price;
  820. if ($agentId) {
  821. $agent = AgentInfo::where('state', 'enable')->find($agentId);
  822. if ($agent) {
  823. $agentProject = $agent->projects()
  824. ->where('state', 'enable')
  825. ->where('project_id', $projectId)
  826. ->first();
  827. if ($agentProject) {
  828. $price = $agentProject->price;
  829. }
  830. }
  831. }
  832. return (float) $price;
  833. }
  834. /**
  835. * 计算优惠券金额
  836. */
  837. private function calculateCouponAmount(): float
  838. {
  839. $couponAmount = 0;
  840. if (request()->has('coupon_id')) {
  841. // TODO: 优惠券逻辑
  842. }
  843. return $couponAmount;
  844. }
  845. /**
  846. * 计算支付金额分配
  847. */
  848. private function calculatePaymentAmounts($user, float $totalAmount, bool $useBalance): array
  849. {
  850. $balanceAmount = 0;
  851. $payAmount = $totalAmount;
  852. if ($useBalance) {
  853. $wallet = $user->wallet;
  854. if (! $wallet) {
  855. throw new Exception('用户钱包不存在');
  856. }
  857. if ($wallet->available_balance >= $totalAmount) {
  858. $balanceAmount = $totalAmount;
  859. $payAmount = 0;
  860. } else {
  861. $balanceAmount = $wallet->available_balance;
  862. $payAmount = bcsub($totalAmount, $balanceAmount, 2);
  863. }
  864. }
  865. return [$balanceAmount, $payAmount];
  866. }
  867. /**
  868. * 获取订单抢单池列表
  869. *
  870. * @param int $orderId 订单ID
  871. * @return array 抢单池列表
  872. */
  873. public function getOrderGrabList(int $orderId): array
  874. {
  875. try {
  876. // 查询订单信息
  877. $order = Order::where('id', $orderId)
  878. ->whereIn('state', [OrderStatus::CREATED->value])
  879. ->firstOrFail();
  880. // 查询抢单池列表
  881. $grabList = $order->grabRecords()->with(['coach.info'])->get();
  882. // 格式化返回数据
  883. $result = [];
  884. foreach ($grabList as $grab) {
  885. $coach = $grab->coach;
  886. $result[] = [
  887. 'id' => $grab->id,
  888. 'coach_id' => $coach->id,
  889. 'nickname' => $coach->info->nickname,
  890. 'avatar' => $coach->info->avatar,
  891. 'distance' => $grab->distance,
  892. 'created_at' => $grab->created_at->format('Y-m-d H:i:s'),
  893. ];
  894. }
  895. return $result;
  896. } catch (\Exception $e) {
  897. Log::error('获取订单抢单池列表失败', [
  898. 'error' => $e->getMessage(),
  899. 'order_id' => $orderId,
  900. ]);
  901. throw $e;
  902. }
  903. }
  904. /**
  905. * 指定技师
  906. */
  907. public function assignCoach(int $userId, int $orderId, int $coachId): bool
  908. {
  909. return DB::transaction(function () use ($userId, $orderId, $coachId) {
  910. try {
  911. // 1. 验证参数
  912. $order = $this->validateAssignOrder($userId, $orderId);
  913. // 2. 验证技师
  914. $coach = $this->validateCoach($coachId);
  915. // 3. 检查抢单池是否已有抢单成功记录
  916. $existsGrabSuccess = $order->grabRecords()
  917. ->where('state', OrderGrabRecordStatus::SUCCEEDED->value)
  918. ->exists();
  919. abort_if($existsGrabSuccess, 400, '该订单已抢单完成');
  920. // 4. 更新订单信息
  921. $this->updateOrderForAssign($order, $coachId);
  922. // 5. 创建订单记录(指派)
  923. $this->createAssignRecord($order, $userId);
  924. // 6. 处理支付
  925. if ($order->payment_type === PaymentMethod::BALANCE->value) {
  926. $this->handleBalancePaymentForAssign($order, $userId, $coachId);
  927. }
  928. return true;
  929. } catch (Exception $e) {
  930. $this->logAssignCoachError($e, $userId, $orderId, $coachId);
  931. throw $e;
  932. }
  933. });
  934. }
  935. /**
  936. * 创建指派记录
  937. */
  938. private function createAssignRecord(Order $order, int $userId): void
  939. {
  940. OrderRecord::create([
  941. 'order_id' => $order->id,
  942. 'object_id' => $userId,
  943. 'object_type' => MemberUser::class,
  944. 'state' => OrderRecordStatus::ASSIGNED->value,
  945. 'remark' => '指定技师',
  946. ]);
  947. }
  948. /**
  949. * 处理指派订单的余额支付
  950. */
  951. private function handleBalancePaymentForAssign(Order $order, int $userId, int $coachId): void
  952. {
  953. // 验证余额
  954. $user = MemberUser::find($userId);
  955. $wallet = $user->wallet;
  956. abort_if($wallet->available_balance < $order->balance_amount, 400, '可用余额不足');
  957. // 扣除余额
  958. $wallet->decrement('total_balance', $order->balance_amount);
  959. $wallet->decrement('available_balance', $order->balance_amount);
  960. // 更新订单状态
  961. $order->update(['state' => OrderStatus::PAID->value]);
  962. // 创建钱包支付记录
  963. WalletPaymentRecord::create([
  964. 'order_id' => $order->id,
  965. 'wallet_id' => $wallet->id,
  966. 'payment_no' => 'balance_'.$order->id,
  967. 'payment_method' => 'balance',
  968. 'total_amount' => $order->balance_amount,
  969. 'actual_amount' => 0,
  970. 'used_wallet_balance' => $order->balance_amount,
  971. 'used_recharge_balance' => 0,
  972. 'state' => 'success',
  973. ]);
  974. // 创建接单记录
  975. // OrderRecord::create([
  976. // 'order_id' => $order->id,
  977. // 'object_id' => $userId,
  978. // 'object_type' => MemberUser::class,
  979. // 'state' => OrderRecordStatus::ASSIGNED->value,
  980. // 'remark' => '已分配技师',
  981. // ]);
  982. // 创建支付成功记录
  983. OrderRecord::create([
  984. 'order_id' => $order->id,
  985. 'object_id' => $userId,
  986. 'object_type' => MemberUser::class,
  987. 'state' => OrderRecordStatus::PAID->value,
  988. 'remark' => '余额支付成功',
  989. ]);
  990. // 更新抢单记录
  991. $this->updateGrabRecords($order->id, $coachId);
  992. }
  993. /**
  994. * 验证指定技师的订单条件
  995. */
  996. private function validateAssignOrder(int $userId, int $orderId): Order
  997. {
  998. // 验证用户状态
  999. $user = MemberUser::where('id', $userId)
  1000. ->where('state', UserStatus::OPEN->value)
  1001. ->firstOrFail();
  1002. // 验证订单状态
  1003. $order = $user->orders()
  1004. ->where('id', $orderId)
  1005. ->whereIn('state', [OrderStatus::CREATED->value, OrderStatus::PAID->value])
  1006. ->lockForUpdate()
  1007. ->firstOrFail();
  1008. return $order;
  1009. }
  1010. /**
  1011. * 更新订单信息
  1012. */
  1013. private function updateOrderForAssign(Order $order, int $coachId): void
  1014. {
  1015. // 修改订单技师
  1016. $order->coach_id = $coachId;
  1017. // 待支付订单需要重新计算金额
  1018. if ($order->state == OrderStatus::CREATED->value) {
  1019. $amounts = $this->calculateOrderAmount(
  1020. $order->user_id,
  1021. $order->address_id,
  1022. $coachId,
  1023. $order->project_id,
  1024. $order->agent_id,
  1025. $order->payment_type === PaymentMethod::BALANCE->value
  1026. );
  1027. // 更新订单金额
  1028. $order->total_amount = $amounts['total_amount'];
  1029. $order->balance_amount = $amounts['balance_amount'];
  1030. $order->pay_amount = $amounts['pay_amount'];
  1031. $order->discount_amount = $amounts['coupon_amount'];
  1032. $order->project_amount = $amounts['project_amount'];
  1033. $order->traffic_amount = $amounts['delivery_fee'];
  1034. }
  1035. $order->save();
  1036. }
  1037. /**
  1038. * 更新抢单记录
  1039. */
  1040. private function updateGrabRecords(int $orderId, int $coachId): void
  1041. {
  1042. OrderGrabRecord::where('order_id', $orderId)
  1043. ->update([
  1044. 'state' => OrderGrabRecordStatus::SUCCEEDED->value,
  1045. 'coach_id' => $coachId,
  1046. ]);
  1047. }
  1048. /**
  1049. * 记录指派技师错误日志
  1050. */
  1051. private function logAssignCoachError(Exception $e, int $userId, int $orderId, int $coachId): void
  1052. {
  1053. Log::error('分配技师失败:', [
  1054. 'message' => $e->getMessage(),
  1055. 'user_id' => $userId,
  1056. 'order_id' => $orderId,
  1057. 'coach_id' => $coachId,
  1058. ]);
  1059. }
  1060. /**
  1061. * 生成订单核销码
  1062. *
  1063. * @param int $userId 用户ID
  1064. * @param int $orderId 订单ID
  1065. * @return array 返回核销码信息
  1066. *
  1067. * @throws Exception
  1068. */
  1069. public function generateVerificationCode(int $userId, int $orderId): array
  1070. {
  1071. try {
  1072. // 1. 验证用户
  1073. $user = MemberUser::where('id', $userId)
  1074. ->where('state', UserStatus::OPEN->value)
  1075. ->firstOrFail();
  1076. // 2. 验证订单状态和归属
  1077. $order = $user->orders()
  1078. ->where('id', $orderId)
  1079. ->whereIn('state', [
  1080. OrderStatus::PAID->value,
  1081. OrderStatus::ACCEPTED->value,
  1082. OrderStatus::DEPARTED->value,
  1083. OrderStatus::ARRIVED->value,
  1084. ])
  1085. ->firstOrFail();
  1086. // 3. 生成时间戳
  1087. $timestamp = time();
  1088. // 4. 生成签名
  1089. $sign = md5("order_{$order->id}_{$timestamp}_".config('app.key'));
  1090. // 5. 组装二维码内容
  1091. $qrCode = "order_{$order->id}_{$timestamp}_{$sign}";
  1092. // 6. 生成二维码图片
  1093. try {
  1094. // 生成JPEG格式的二维码
  1095. $qrCodeImage = QrCode::format('png')
  1096. // 设置二维码大小为200px
  1097. ->size(200)
  1098. // 设置二维码边距为1
  1099. ->margin(1)
  1100. // 设置纠错级别为最高级别H
  1101. ->errorCorrection('H')
  1102. // 生成二维码图片
  1103. ->generate($qrCode);
  1104. // 将JPEG转为base64
  1105. $qrCodeBase64 = 'data:image/png;base64,'.base64_encode($qrCodeImage);
  1106. } catch (Exception $e) {
  1107. Log::error('生成二维码图片失败:', [
  1108. 'order_id' => $orderId,
  1109. 'error' => $e->getMessage(),
  1110. ]);
  1111. // 如果二维码生成失败,仍然返回文本内容
  1112. $qrCodeBase64 = null;
  1113. }
  1114. // 7. 返回结果
  1115. return [
  1116. 'order_id' => $order->id,
  1117. 'qr_code' => $qrCode,
  1118. 'qr_image' => $qrCodeBase64,
  1119. 'expired_at' => date('Y-m-d H:i:s', $timestamp + 300),
  1120. 'state' => $order->state,
  1121. ];
  1122. } catch (Exception $e) {
  1123. Log::error('生成订单核销码失败:', [
  1124. 'user_id' => $userId,
  1125. 'order_id' => $orderId,
  1126. 'error' => $e->getMessage(),
  1127. 'trace' => $e->getTraceAsString(),
  1128. ]);
  1129. throw $e;
  1130. }
  1131. }
  1132. /**
  1133. * 验证核销码
  1134. *
  1135. * @param string $qrCode 核销码
  1136. * @param int $orderId 订单ID
  1137. *
  1138. * @throws Exception
  1139. */
  1140. public function verifyCode(string $qrCode, int $orderId): bool
  1141. {
  1142. try {
  1143. // 1. 验证订单
  1144. $order = Order::where('id', $orderId)
  1145. ->where('state', OrderStatus::PAID->value)
  1146. ->firstOrFail();
  1147. // 2. 解析二维码
  1148. // 二维码格式: order_{order_id}_{timestamp}_{sign}
  1149. $parts = explode('_', $qrCode);
  1150. abort_if(count($parts) !== 4, 400, '二维码格式错误');
  1151. [$prefix, $scanOrderId, $timestamp, $sign] = $parts;
  1152. // 3. 验证前缀
  1153. abort_if($prefix !== 'order', 400, '无效的二维码');
  1154. // 4. 验证订单ID
  1155. abort_if((int) $scanOrderId !== $order->id, 400, '二维码与订单不匹配');
  1156. // 5. 验证时间戳(二维码5分钟内有效)
  1157. $qrTimestamp = (int) $timestamp;
  1158. $now = time();
  1159. abort_if($now - $qrTimestamp > 300, 400, '二维码已过期');
  1160. // 6. 验证签名
  1161. $correctSign = md5("order_{$order->id}_{$timestamp}_".config('app.key'));
  1162. abort_if($sign !== $correctSign, 400, '二维码签名错误');
  1163. return true;
  1164. } catch (Exception $e) {
  1165. Log::error('验证核销码失败:', [
  1166. 'order_id' => $orderId,
  1167. 'qr_code' => $qrCode,
  1168. 'error' => $e->getMessage(),
  1169. 'trace' => $e->getTraceAsString(),
  1170. ]);
  1171. throw $e;
  1172. }
  1173. }
  1174. }