Browse Source

feat:后端-订单管理

刘学玺 4 months ago
parent
commit
44e01329cd

+ 27 - 32
app/Admin/Controllers/MemberUserController.php

@@ -3,6 +3,7 @@
 namespace App\Admin\Controllers;
 
 use App\Services\MemberUserService;
+use Illuminate\Http\Request;
 use Slowlyo\OwlAdmin\Controllers\AdminController;
 
 /**
@@ -73,38 +74,6 @@ class MemberUserController extends AdminController
         ]);
     }
 
-    /**
-     * [用户]获取用户详情
-     */
-    // public function getDetail($id)
-    // {
-    //     return $this->detail($id);
-    // }
-
-    // /**
-    //  * 新增用户
-    //  */
-    // public function store()
-    // {
-    //     return $this->save();
-    // }
-
-    // /**
-    //  * 更新用户
-    //  */
-    // public function update($id)
-    // {
-    //     return $this->save($id);
-    // }
-
-    // /**
-    //  * 删除用户
-    //  */
-    // public function destroy($id)
-    // {
-    //     return $this->delete($id);
-    // }
-
     /**
      * 用户-用户列表查询
      *
@@ -148,4 +117,30 @@ class MemberUserController extends AdminController
 
         return $this->service->manageUserList($viewName, $whereParams, $pageParams, $sortParams);
     }
+
+    /**
+     * [用户管理]拉黑用户
+     *
+     * @description 将用户加入黑名单
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam user_id integer required 用户ID Example: 1
+     * @bodyParam reason string required 拉黑原因 Example: 恶意投诉
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "拉黑成功",
+     *   "data": null
+     * }
+     */
+    public function blockUser(Request $request)
+    {
+        $validated = $request->validate([
+            'user_id' => 'required|integer|exists:member_users,id',
+            'reason' => 'required|string|max:255',
+        ]);
+
+        return $this->service->blockUser($validated);
+    }
 }

+ 251 - 0
app/Admin/Controllers/OrderController.php

@@ -3,6 +3,7 @@
 namespace App\Admin\Controllers;
 
 use App\Services\OrderService;
+use Illuminate\Http\Request;
 use Slowlyo\OwlAdmin\Controllers\AdminController;
 
 /**
@@ -110,4 +111,254 @@ class OrderController extends AdminController
             amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
         ]);
     }
+
+    /**
+     * [订单管理]更换订单技师
+     *
+     * @description 更换订单的服务技师
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam order_id integer required 订单ID Example: 1
+     * @bodyParam new_coach_id integer required 新技师ID Example: 2
+     * @bodyParam reason string optional 更换原因 Example: 技师临时有事
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "更换技师成功",
+     *   "data": null
+     * }
+     */
+    public function changeCoach(Request $request)
+    {
+        $validated = $request->validate([
+            'order_id' => 'required|integer|exists:order,id',
+            'new_coach_id' => 'required|integer|exists:coach_users,id',
+            'reason' => 'nullable|string|max:255',
+        ]);
+
+        return $this->service->changeOrderCoach($validated);
+    }
+
+    /**
+     * [订单管理]指定抢单技师
+     *
+     * @description 为订单指定抢单技师
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam order_id integer required 订单ID Example: 1
+     * @bodyParam coach_id integer required 技师ID Example: 2
+     * @bodyParam remark string optional 备注 Example: 平台指派
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "指定技师成功",
+     *   "data": null
+     * }
+     */
+    public function assignCoach(Request $request)
+    {
+        $validated = $request->validate([
+            'order_id' => 'required|integer|exists:order,id',
+            'coach_id' => 'required|integer|exists:coach_users,id',
+            'remark' => 'nullable|string|max:255',
+        ]);
+
+        return $this->service->assignOrderCoach($validated);
+    }
+
+    /**
+     * [订单管理]获取订单抢单技师列表
+     *
+     * @description 获取参与抢单的技师列表
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @urlParam order_id integer required 订单ID Example: 1
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": [{
+     *     "id": 1,
+     *     "coach_id": 100,
+     *     "nickname": "技师A",
+     *     "avatar": "https://example.com/avatar.jpg",
+     *     "distance": "1.5",
+     *     "state": 1,
+     *     "created_at": "2024-03-20 10:00:00"
+     *   }]
+     * }
+     */
+    public function getOrderGrabList(Request $request, $orderId)
+    {
+        return $this->service->getOrderGrabList($orderId);
+    }
+
+    /**
+     * [订单管理]获取附近技师列表
+     *
+     * @description 获取订单附近的技师列表
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @urlParam orderId integer required 订单ID Example: 1
+     *
+     * @queryParam distance integer optional 搜索范围(米) Example: 5000
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": [{
+     *     "id": 1,
+     *     "nickname": "技师A",
+     *     "avatar": "https://example.com/avatar.jpg",
+     *     "distance": "1.5",
+     *     "work_status": 1,
+     *     "work_status_text": "空闲",
+     *     "rating": "4.8",
+     *     "order_count": 100,
+     *     "created_at": "2024-03-20 10:00:00"
+     *   }]
+     * }
+     */
+    public function getNearbyCoaches(Request $request, $orderId)
+    {
+        $validated = $request->validate([
+            'distance' => 'nullable|integer|min:1000|max:50000',
+        ]);
+
+        return $this->service->getNearbyCoaches($orderId, $validated['distance'] ?? 5000);
+    }
+
+    /**
+     * [订单管理]重置订单技师
+     *
+     * @description 重置订单技师,将订单改为抢单模式
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam order_id integer required 订单ID Example: 1
+     * @bodyParam reason string optional 重置原因 Example: 技师无法服务
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "重置技师成功",
+     *   "data": null
+     * }
+     */
+    public function resetCoach(Request $request)
+    {
+        $validated = $request->validate([
+            'order_id' => 'required|integer|exists:order,id',
+            'reason' => 'nullable|string|max:255',
+        ]);
+
+        return $this->service->resetOrderCoach($validated);
+    }
+
+    /**
+     * [订单管理]临时接单
+     *
+     * @description 管理员代替技师接单
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam order_id integer required 订单ID Example: 1
+     * @bodyParam reason string optional 接单原因 Example: 客户要求指定技师
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "接单成功",
+     *   "data": null
+     * }
+     */
+    public function temporaryAccept(Request $request)
+    {
+        $validated = $request->validate([
+            'order_id' => 'required|integer|exists:order,id',
+            'reason' => 'nullable|string|max:255',
+        ]);
+
+        return $this->service->temporaryAcceptOrder($validated);
+    }
+
+    /**
+     * [订单管理]开始服务
+     *
+     * @description 管理员代替技师开始服务
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam order_id integer required 订单ID Example: 1
+     * @bodyParam reason string optional 原因 Example: 用户核销码无法扫描
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "开始服务成功",
+     *   "data": null
+     * }
+     */
+    public function startService(Request $request)
+    {
+        $validated = $request->validate([
+            'order_id' => 'required|integer|exists:order,id',
+            'reason' => 'nullable|string|max:255',
+        ]);
+
+        return $this->service->startOrderService($validated);
+    }
+
+    /**
+     * [订单管理]结束服务
+     *
+     * @description 管理员代替技师结束服务
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam order_id integer required 订单ID Example: 1
+     * @bodyParam reason string optional 原因 Example: 用户要求提前结束服务
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "结束服务成功",
+     *   "data": null
+     * }
+     */
+    public function endService(Request $request)
+    {
+        $validated = $request->validate([
+            'order_id' => 'required|integer|exists:order,id',
+            'reason' => 'nullable|string|max:255',
+        ]);
+
+        return $this->service->endOrderService($validated);
+    }
+
+    /**
+     * [订单管理]处理报警
+     *
+     * @description 处理技师报警事件
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam exc_record_id integer required 异常记录ID Example: 1
+     * @bodyParam handle_result string required 处理结果 Example: 已电话联系技师,确认安全
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "处理成功",
+     *   "data": null
+     * }
+     */
+    public function handleAlarm(Request $request)
+    {
+        $validated = $request->validate([
+            'exc_record_id' => 'required|integer|exists:order_exc_records,id',
+            'handle_result' => 'required|string|max:255',
+        ]);
+
+        return $this->service->handleOrderAlarm($validated);
+    }
 }

+ 24 - 81
app/Enums/OrderRecordStatus.php

@@ -72,6 +72,26 @@ enum OrderRecordStatus: int
      */
     case REFUNDED = 13;
 
+    /**
+     * 记录状态��更换技师
+     */
+    case CHANGE_COACH = 14;
+
+    /**
+     * 记录状态:重置技师
+     */
+    case RESET_COACH = 15;
+
+    /**
+     * 记录状态:临时接单
+     */
+    case TEMPORARY_ACCEPTED = 16;
+
+    /**
+     * 记录状态:报警已处理
+     */
+    case ALARM_HANDLED = 17;
+
     /**
      * 获取状态的显示文本
      *
@@ -93,87 +113,10 @@ enum OrderRecordStatus: int
             self::LEFT => '技师撤离',
             self::REFUNDING => '退款中',
             self::REFUNDED => '退款完成',
+            self::CHANGE_COACH => '更换技师',
+            self::RESET_COACH => '重置技师',
+            self::TEMPORARY_ACCEPTED => '平台临时接单',
+            self::ALARM_HANDLED => '报警已处理',
         };
     }
-
-    /**
-     * 获取状态的整数值
-     *
-     * @return int 状态值
-     */
-    public function value(): int
-    {
-        return $this->value;
-    }
-
-    /**
-     * 检查当前状态是否与指定状态相同
-     *
-     * @param  self  $status  要比较的状态
-     * @return bool 如果状态相同返回 true,否则返回 false
-     */
-    public function is(self $status): bool
-    {
-        return $this === $status;
-    }
-
-    /**
-     * 根据整数值创建对应的状态枚举实例
-     *
-     * @param  int  $value  状态值
-     * @return self|null 返回对应的状态枚举实例,如果值无效则返回 null
-     */
-    public static function fromValue(int $value): ?self
-    {
-        return match ($value) {
-            self::CREATED->value => self::CREATED,
-            self::PAID->value => self::PAID,
-            self::ASSIGNED->value => self::ASSIGNED,
-            self::ARRIVED->value => self::ARRIVED,
-            self::STARTED->value => self::STARTED,
-            self::COMPLETED->value => self::COMPLETED,
-            self::EVALUATED->value => self::EVALUATED,
-            self::CANCELLED->value => self::CANCELLED,
-            self::REJECTED->value => self::REJECTED,
-            self::DEPARTED->value => self::DEPARTED,
-            self::LEFT->value => self::LEFT,
-            self::REFUNDING->value => self::REFUNDING,
-            self::REFUNDED->value => self::REFUNDED,
-            default => null
-        };
-    }
-
-    /**
-     * 获取所有状态的值数组
-     *
-     * @return array 包含所有状态值的数组
-     */
-    public static function values(): array
-    {
-        return array_column(self::cases(), 'value');
-    }
-
-    /**
-     * 获取所有状态的键值对数组
-     *
-     * @return array 状态值作为键,显示文本作为值的关联数组
-     */
-    public static function all(): array
-    {
-        return [
-            self::CREATED->value => self::CREATED->label(),
-            self::PAID->value => self::PAID->label(),
-            self::ASSIGNED->value => self::ASSIGNED->label(),
-            self::ARRIVED->value => self::ARRIVED->label(),
-            self::STARTED->value => self::STARTED->label(),
-            self::COMPLETED->value => self::COMPLETED->label(),
-            self::EVALUATED->value => self::EVALUATED->label(),
-            self::CANCELLED->value => self::CANCELLED->label(),
-            self::REJECTED->value => self::REJECTED->label(),
-            self::DEPARTED->value => self::DEPARTED->label(),
-            self::LEFT->value => self::LEFT->label(),
-            self::REFUNDING->value => self::REFUNDING->label(),
-            self::REFUNDED->value => self::REFUNDED->label(),
-        ];
-    }
 }

+ 42 - 0
app/Services/MemberUserService.php

@@ -2,7 +2,9 @@
 
 namespace App\Services;
 
+use App\Enums\UserStatus;
 use App\Models\MemberUser;
+use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Slowlyo\OwlAdmin\Services\AdminService;
@@ -57,4 +59,44 @@ class MemberUserService extends AdminService
             ]);
         }
     }
+
+    /**
+     * 拉黑用户
+     *
+     * @param  array  $data  包含 user_id 和 reason
+     */
+    public function blockUser(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取用户信息
+            $user = MemberUser::findOrFail($data['user_id']);
+
+            // 验证用户当前状态
+            abort_if($user->state === UserStatus::CLOSE->value, 422, '用户已经被拉黑');
+
+            // 更新用户状态为拉黑
+            $user->state = UserStatus::CLOSE->value;
+            $user->save();
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '拉黑成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('拉黑用户失败', [
+                'user_id' => $data['user_id'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
 }

+ 506 - 2
app/Services/OrderService.php

@@ -2,7 +2,17 @@
 
 namespace App\Services;
 
+use App\Enums\OrderRecordStatus;
+use App\Enums\OrderStatus;
+use App\Enums\TechnicianWorkStatus;
+use App\Models\CoachLocation;
 use App\Models\Order;
+use App\Models\OrderExcRecord;
+use App\Models\OrderGrabRecord;
+use App\Models\OrderRecord;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 use Slowlyo\OwlAdmin\Services\AdminService;
 
 /**
@@ -13,5 +23,499 @@ use Slowlyo\OwlAdmin\Services\AdminService;
  */
 class OrderService extends AdminService
 {
-	protected string $modelName = Order::class;
-}
+    protected string $modelName = Order::class;
+
+    /**
+     * 更换订单技师
+     *
+     * @param  array  $data  包含 order_id, new_coach_id 和可选的 reason
+     */
+    public function changeOrderCoach(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取订单信息
+            $order = Order::findOrFail($data['order_id']);
+
+            // 验证订单状态是否允许更换技师
+            $allowedStates = [
+                OrderStatus::PAID->value,
+                OrderStatus::ACCEPTED->value,
+                OrderStatus::DEPARTED->value,
+                OrderStatus::ARRIVED->value,
+            ];
+
+            abort_if(! in_array($order->state, $allowedStates), 422, '当前订单状态不允许更换技师');
+
+            // 记录原技师ID
+            $oldCoachId = $order->coach_id;
+
+            // 更新订单技师
+            $order->coach_id = $data['new_coach_id'];
+            $order->save();
+
+            // 记录技师更换历史
+            OrderRecord::create([
+                'order_id' => $order->id,
+                'object_id' => Auth::user()->id,
+                'object_type' => 'admin',
+                'old_coach_id' => $oldCoachId,
+                'new_coach_id' => $data['new_coach_id'],
+                'state' => OrderRecordStatus::CHANGE_COACH->value,
+                'remark' => $data['reason'] ?? null,
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '更换技师成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('更换订单技师失败', [
+                'order_id' => $data['order_id'],
+                'new_coach_id' => $data['new_coach_id'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 指定抢单技师
+     *
+     * @param  array  $data  包含 order_id, coach_id 和可选的 remark
+     */
+    public function assignOrderCoach(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取订单信息
+            $order = Order::findOrFail($data['order_id']);
+
+            // 验证订单状态是否为待抢单
+            abort_if($order->state !== OrderStatus::CREATED->value, 422, '当前订单状态不允许指定技师');
+
+            // 更新订单技师和状态
+            $order->coach_id = $data['coach_id'];
+            $order->state = OrderStatus::ASSIGNED->value;
+            $order->save();
+
+            // 记录指定技师历史
+            OrderRecord::create([
+                'order_id' => $order->id,
+                'object_id' => Auth::user()->id,
+                'object_type' => 'admin',
+                'old_coach_id' => null,
+                'new_coach_id' => $data['coach_id'],
+                'state' => OrderRecordStatus::ASSIGNED->value,
+                'remark' => $data['remark'] ?? '平台指派',
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '指定技师成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('指定抢单技师失败', [
+                'order_id' => $data['order_id'],
+                'coach_id' => $data['coach_id'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取订单抢单技师列表
+     *
+     * @param  int  $orderId  订单ID
+     */
+    public function getOrderGrabList(int $orderId): array
+    {
+        try {
+            // 获取订单信息
+            $order = Order::findOrFail($orderId);
+
+            // 获取抢单记录
+            $grabRecords = OrderGrabRecord::with(['coach', 'coach.info'])
+                ->where('order_id', $orderId)
+                ->orderBy('created_at', 'desc')
+                ->get();
+
+            $data = $grabRecords->map(function ($record) {
+                return [
+                    'id' => $record->id,
+                    'coach_id' => $record->coach_id,
+                    'nickname' => $record->coach->info->nickname ?? '',
+                    'avatar' => $record->coach->info->avatar ?? '',
+                    'distance' => $record->distance,
+                    'state' => $record->state,
+                    'created_at' => $record->created_at->format('Y-m-d H:i:s'),
+                ];
+            });
+
+            return [
+                'code' => 200,
+                'message' => '获取成功',
+                'data' => $data,
+            ];
+
+        } catch (\Exception $e) {
+            Log::error('获取订单抢单技师列表失败', [
+                'order_id' => $orderId,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取附近技师列表
+     *
+     * @param  int  $orderId  订单ID
+     * @param  int  $distance  搜索范围(米)
+     */
+    public function getNearbyCoaches(int $orderId, int $distance = 5000): array
+    {
+        try {
+            // 获取订单信息
+            $order = Order::findOrFail($orderId);
+
+            // 获取附近技师
+            // TODO: 优化查询
+            $nearbyCoaches = CoachLocation::with(['coach', 'coach.info', 'coach.score'])
+                ->selectRaw('*, ST_Distance_Sphere(
+                    point(longitude, latitude),
+                    point(?, ?)
+                ) as distance', [$order->longitude, $order->latitude])
+                ->having('distance', '<=', $distance)
+                ->orderBy('distance')
+                ->get();
+
+            $data = $nearbyCoaches->map(function ($location) {
+                return [
+                    'id' => $location->coach->id,
+                    'nickname' => $location->coach->info->nickname ?? '',
+                    'avatar' => $location->coach->info->avatar ?? '',
+                    'distance' => round($location->distance / 1000, 1), // 转换公里
+                    'work_status' => $location->coach->work_status,
+                    'work_status_text' => TechnicianWorkStatus::fromValue($location->coach->work_status)->label(),
+                    'rating' => $location->coach->score->rating_score ?? 5.0,
+                    'order_count' => $location->coach->score->order_count ?? 0,
+                    'created_at' => $location->coach->created_at->format('Y-m-d H:i:s'),
+                ];
+            });
+
+            return [
+                'code' => 200,
+                'message' => '获取成功',
+                'data' => $data,
+            ];
+
+        } catch (\Exception $e) {
+            Log::error('获取附近技师列表失败', [
+                'order_id' => $orderId,
+                'distance' => $distance,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 重置订单技师
+     *
+     * @param  array  $data  包含 order_id 和可选的 reason
+     */
+    public function resetOrderCoach(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取订单信息
+            $order = Order::findOrFail($data['order_id']);
+
+            // 验证订单状态是否允许重置技师
+            $allowedStates = [
+                OrderStatus::PAID->value,
+                OrderStatus::ACCEPTED->value,
+                OrderStatus::DEPARTED->value,
+                OrderStatus::ARRIVED->value,
+            ];
+
+            abort_if(! in_array($order->state, $allowedStates), 422, '当前订单状态不允许重置技师');
+
+            // 记录原技师ID
+            $oldCoachId = $order->coach_id;
+
+            // 更新订单信息
+            $order->coach_id = null;
+            $order->state = OrderStatus::PAID->value; // 重置为支付状态
+            $order->save();
+
+            // 记录技师重置历史
+            OrderRecord::create([
+                'order_id' => $order->id,
+                'object_id' => Auth::user()->id,
+                'object_type' => 'admin',
+                'old_coach_id' => $oldCoachId,
+                'new_coach_id' => null,
+                'state' => OrderRecordStatus::RESET_COACH->value,
+                'remark' => $data['reason'] ?? null,
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '重置技师成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('重置订单技师失败', [
+                'order_id' => $data['order_id'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 临时接单
+     *
+     * @param  array  $data  包含 order_id 和可选的 reason
+     */
+    public function temporaryAcceptOrder(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取订单信息
+            $order = Order::findOrFail($data['order_id']);
+
+            // 验证订单状态是否为已支付状态
+            abort_if($order->state !== OrderStatus::PAID->value, 422, '当前订单状态不允许接单');
+
+            // 验证订单必须已指定技师
+            abort_if(empty($order->coach_id), 422, '订单未指定技师');
+
+            // 更新订单信息
+            $order->state = OrderStatus::ACCEPTED->value; // 更新为已接单状态
+            $order->save();
+
+            // 记录临时接单历史
+            OrderRecord::create([
+                'order_id' => $order->id,
+                'object_id' => Auth::user()->id,
+                'object_type' => 'admin',
+                'old_coach_id' => null,
+                'new_coach_id' => $order->coach_id,
+                'state' => OrderRecordStatus::TEMPORARY_ACCEPTED->value,
+                'remark' => $data['reason'] ?? '平台临时接单',
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '接单成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('临时接单失败', [
+                'order_id' => $data['order_id'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 开始服务
+     *
+     * @param  array  $data  包含 order_id 和可选的 reason
+     */
+    public function startOrderService(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取订单信息
+            $order = Order::findOrFail($data['order_id']);
+
+            // 验证订单状态是否允许开始服务
+            $allowedStates = [
+                OrderStatus::ACCEPTED->value,
+                OrderStatus::DEPARTED->value,
+                OrderStatus::ARRIVED->value,
+            ];
+
+            abort_if(! in_array($order->state, $allowedStates), 422, '当前订单状态不允许开始服务');
+
+            // 更新订单状态
+            $order->state = OrderStatus::STARTED->value;
+            $order->save();
+
+            // 记录开始服务历史
+            OrderRecord::create([
+                'order_id' => $order->id,
+                'object_id' => Auth::user()->id,
+                'object_type' => 'admin',
+                'old_coach_id' => $order->coach_id,
+                'new_coach_id' => $order->coach_id,
+                'state' => OrderRecordStatus::STARTED->value,
+                'remark' => $data['reason'] ?? '平台代扫核销码',
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '开始服务成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('开始服务失败', [
+                'order_id' => $data['order_id'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 结束服务
+     *
+     * @param  array  $data  包含 order_id 和可选的 reason
+     */
+    public function endOrderService(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取订单信息
+            $order = Order::findOrFail($data['order_id']);
+
+            // 验证订单状态是否为已开始服务状态
+            abort_if($order->state !== OrderStatus::STARTED->value, 422, '当前订单状态不允许结束服务');
+
+            // 更新订单状态
+            $order->state = OrderStatus::LEFT->value;
+            $order->save();
+
+            // 记录结束服务历史
+            OrderRecord::create([
+                'order_id' => $order->id,
+                'object_id' => Auth::user()->id,
+                'object_type' => 'admin',
+                'old_coach_id' => $order->coach_id,
+                'new_coach_id' => $order->coach_id,
+                'state' => OrderRecordStatus::LEFT->value,
+                'remark' => $data['reason'] ?? '平台代确认撤离',
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '结束服务成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('结束服务失败', [
+                'order_id' => $data['order_id'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 处理报警
+     *
+     * @param  array  $data  包含 exc_record_id 和 handle_result
+     */
+    public function handleOrderAlarm(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取并验证异常记录
+            $excRecord = OrderExcRecord::where('id', $data['exc_record_id'])
+                ->firstOrFail();
+
+            // 获取关联的订单信息
+            $order = Order::findOrFail($excRecord->order_id);
+
+            // 更新异常记录状态
+            $excRecord->handle_result = $data['handle_result'];
+            $excRecord->handler_id = Auth::user()->id;
+            $excRecord->handle_time = now();
+            $excRecord->save();
+
+            // 记录处理历史
+            OrderRecord::create([
+                'order_id' => $order->id,
+                'object_id' => Auth::user()->id,
+                'object_type' => 'admin',
+                'old_coach_id' => $order->coach_id,
+                'new_coach_id' => $order->coach_id,
+                'state' => OrderRecordStatus::ALARM_HANDLED->value,
+                'remark' => $data['handle_result'],
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '处理成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('处理报警失败', [
+                'exc_record_id' => $data['exc_record_id'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+}

+ 4 - 4
doc/系统设计/数据库设计/物理模型/mymd

@@ -7,10 +7,10 @@ rm xiaoding_test.md
 mv xiaoding_test_$(date +%Y%m%d_%H%M%S).md xiaoding_test.md
 
 echo "---
-html:  
-    embed_local_images: false  
-    embed_svg: true  
-    offline: false  
+html:
+    embed_local_images: false
+    embed_svg: true
+    offline: false
     toc: false
 print_background: false
 export_on_save:

File diff suppressed because it is too large
+ 696 - 102
doc/系统设计/数据库设计/物理模型/xiaoding_test.md


+ 16 - 0
routes/web.php

@@ -1,5 +1,7 @@
 <?php
 
+use App\Admin\Controllers\MemberUserController;
+use App\Admin\Controllers\OrderController;
 use App\Http\Controllers\EnumController;
 use Illuminate\Routing\Router;
 use Illuminate\Support\Facades\Route;
@@ -85,6 +87,8 @@ Route::group(
             Route::put('/{id}', [App\Admin\Controllers\ProjectController::class, 'updateProject']);
             Route::delete('/{id}', [App\Admin\Controllers\ProjectController::class, 'deleteProject']);
         });
+
+        Route::post('order/change-coach', [OrderController::class, 'changeCoach']);
     }
 );
 
@@ -153,4 +157,16 @@ Route::group([
         // 获取设置值详情
         Route::get('/{id}', [\App\Admin\Controllers\SettingValueController::class, 'getValueDetail']);
     });
+
+    Route::middleware(['auth:sanctum'])->group(function () {
+        Route::post('order/change-coach', [OrderController::class, 'changeCoach']);
+        Route::get('order/{orderId}/grab-list', [OrderController::class, 'getOrderGrabList']);
+        Route::get('order/{orderId}/nearby-coaches', [OrderController::class, 'getNearbyCoaches']);
+        Route::post('order/reset-coach', [OrderController::class, 'resetCoach']);
+        Route::post('order/temporary-accept', [OrderController::class, 'temporaryAccept']);
+        Route::post('order/start-service', [OrderController::class, 'startService']);
+        Route::post('order/end-service', [OrderController::class, 'endService']);
+        Route::post('order/handle-alarm', [OrderController::class, 'handleAlarm']);
+        Route::post('user/block', [MemberUserController::class, 'blockUser']);
+    });
 });

Some files were not shown because too many files changed in this diff