|
@@ -2,12 +2,15 @@
|
|
|
|
|
|
namespace App\Services\Coach;
|
|
|
|
|
|
+use App\Enums\OrderGrabRecordStatus;
|
|
|
use App\Enums\OrderStatus;
|
|
|
+use App\Enums\ProjectStatus;
|
|
|
use App\Enums\TechnicianAuthStatus;
|
|
|
use App\Enums\TechnicianLocationType;
|
|
|
use App\Enums\TechnicianStatus;
|
|
|
use App\Models\MemberUser;
|
|
|
use App\Models\Order;
|
|
|
+use App\Models\OrderGrabRecord;
|
|
|
use App\Services\SettingItemService;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
@@ -15,6 +18,10 @@ use Illuminate\Support\Facades\Redis;
|
|
|
|
|
|
class OrderService
|
|
|
{
|
|
|
+ private const DEFAULT_PER_PAGE = 10;
|
|
|
+
|
|
|
+ private const MAX_DISTANCE = 40; // 最大距离限制(公里)
|
|
|
+
|
|
|
private SettingItemService $settingService;
|
|
|
|
|
|
public function __construct(SettingItemService $settingService)
|
|
@@ -22,6 +29,57 @@ class OrderService
|
|
|
$this->settingService = $settingService;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 获取技师的订单列表
|
|
|
+ *
|
|
|
+ * @param int $userId 技师用户ID
|
|
|
+ * @param array $params 请求参数
|
|
|
+ * @return \Illuminate\Pagination\LengthAwarePaginator
|
|
|
+ */
|
|
|
+ public function getOrderList(int $userId, array $params)
|
|
|
+ {
|
|
|
+ return DB::transaction(function () use ($userId, $params) {
|
|
|
+ try {
|
|
|
+ // 加载技师用户信息
|
|
|
+ $user = MemberUser::findOrFail($userId);
|
|
|
+ $coach = $user->coach;
|
|
|
+ abort_if(! $coach, 404, '技师不存在');
|
|
|
+
|
|
|
+ // 构建订单查询
|
|
|
+ $query = $coach->orders()->whereNotIn('state', [
|
|
|
+ OrderStatus::CREATED->value,
|
|
|
+ OrderStatus::ASSIGNED->value,
|
|
|
+ ])
|
|
|
+ ->orderBy('created_at', 'desc');
|
|
|
+
|
|
|
+ // 分页获取数据
|
|
|
+ $paginator = $query->paginate(
|
|
|
+ $params['per_page'] ?? 10,
|
|
|
+ ['*'],
|
|
|
+ 'page',
|
|
|
+ $params['page'] ?? 1
|
|
|
+ );
|
|
|
+
|
|
|
+ // 需要加载关联数据
|
|
|
+ $items = $paginator->items();
|
|
|
+ $query->with(['project']); // 加载项目信息以便返回project_name等字段
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'items' => $items,
|
|
|
+ 'total' => $paginator->total(),
|
|
|
+ ];
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('获取技师订单列表失败', [
|
|
|
+ 'user_id' => $userId,
|
|
|
+ 'error_message' => $e->getMessage(),
|
|
|
+ 'error_trace' => $e->getTraceAsString(),
|
|
|
+ ]);
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取可抢订单列表
|
|
|
*
|
|
@@ -256,4 +314,105 @@ class OrderService
|
|
|
$order->project->final_price = ($order->project->final_price ?? $order->project->price)
|
|
|
+ $order->project->traffic_fee;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 技师抢单
|
|
|
+ *
|
|
|
+ * @param int $userId 技师用户ID
|
|
|
+ * @param int $orderId 订单ID
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ public function grabOrder(int $userId, int $orderId)
|
|
|
+ {
|
|
|
+ return DB::transaction(function () use ($userId, $orderId) {
|
|
|
+ try {
|
|
|
+ // 加载用户和技师信息
|
|
|
+ $user = MemberUser::with([
|
|
|
+ 'coach',
|
|
|
+ 'coach.info',
|
|
|
+ 'coach.real',
|
|
|
+ 'coach.qual',
|
|
|
+ 'coach.locations',
|
|
|
+ 'coach.projects',
|
|
|
+ ])->findOrFail($userId);
|
|
|
+
|
|
|
+ // 验证技师信息
|
|
|
+ [$coach, $location] = $this->validateCoach($user);
|
|
|
+
|
|
|
+ // 获取订单信息
|
|
|
+ $order = Order::lockForUpdate()->findOrFail($orderId);
|
|
|
+
|
|
|
+ // 验证订单状态
|
|
|
+ abort_if($order->state !== OrderStatus::CREATED->value, 400, '订单状态异常,无法抢单');
|
|
|
+
|
|
|
+ // 检查技师是否已参与抢单
|
|
|
+ $existingGrab = $coach->grabRecords()
|
|
|
+ ->where('order_id', $orderId)
|
|
|
+ ->first();
|
|
|
+ abort_if($existingGrab, 400, '您已参与抢单,请勿重复操作');
|
|
|
+
|
|
|
+ // 验证订单是否在技师服务范围内
|
|
|
+ // 通过Redis GEO计算订单与技师的距离
|
|
|
+ $distance = Redis::geodist(
|
|
|
+ 'order_locations',
|
|
|
+ 'order:'.$order->id,
|
|
|
+ $location->longitude.','.$location->latitude,
|
|
|
+ 'km'
|
|
|
+ ) ?? PHP_FLOAT_MAX;
|
|
|
+ abort_if($distance > self::MAX_DISTANCE, 400, '订单超出服务范围');
|
|
|
+
|
|
|
+ // 验证技师是否具备该项目服务资格
|
|
|
+ $coachProject = $coach->projects()
|
|
|
+ ->where('project_id', $order->project_id)
|
|
|
+ ->where('state', ProjectStatus::OPEN->value)
|
|
|
+ ->first();
|
|
|
+ abort_if(! $coachProject, 400, '未开通该项目服务资格');
|
|
|
+
|
|
|
+ // 添加抢单记录
|
|
|
+ OrderGrabRecord::create([
|
|
|
+ 'order_id' => $order->id,
|
|
|
+ 'coach_id' => $coach->id,
|
|
|
+ 'distance' => $distance,
|
|
|
+ 'state' => OrderGrabRecordStatus::JOINED->value,
|
|
|
+ 'created_at' => now(),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 记录日志
|
|
|
+ Log::info('技师参与抢单', [
|
|
|
+ 'user_id' => $userId,
|
|
|
+ 'coach_id' => $coach->id,
|
|
|
+ 'order_id' => $orderId,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'message' => '已参与抢单',
|
|
|
+ 'order_id' => $orderId,
|
|
|
+ ];
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('技师参与抢单失败', [
|
|
|
+ 'user_id' => $userId,
|
|
|
+ 'order_id' => $orderId,
|
|
|
+ 'error_message' => $e->getMessage(),
|
|
|
+ 'error_trace' => $e->getTraceAsString(),
|
|
|
+ ]);
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算两点之间的距离(公里)
|
|
|
+ */
|
|
|
+ private function calculateDistance($lat1, $lon1, $lat2, $lon2): float
|
|
|
+ {
|
|
|
+ $theta = $lon1 - $lon2;
|
|
|
+ $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1))
|
|
|
+ * cos(deg2rad($lat2)) * cos(deg2rad($theta));
|
|
|
+ $dist = acos($dist);
|
|
|
+ $dist = rad2deg($dist);
|
|
|
+ $miles = $dist * 60 * 1.1515;
|
|
|
+
|
|
|
+ return round($miles * 1.609344, 1); // 转换为公里并保留一位小数
|
|
|
+ }
|
|
|
}
|